diff options
Diffstat (limited to 'media/mca/filterpacks')
87 files changed, 11705 insertions, 0 deletions
diff --git a/media/mca/filterpacks/Android.mk b/media/mca/filterpacks/Android.mk new file mode 100644 index 0000000..6166b1e --- /dev/null +++ b/media/mca/filterpacks/Android.mk @@ -0,0 +1,55 @@ +# Copyright (C) 2011 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH := $(call my-dir) + +## +# base +## +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_MODULE := libfilterpack_base +LOCAL_SRC_FILES := native/base/geometry.cpp \ + native/base/time_util.cpp + +LOCAL_CFLAGS := -DANDROID + +include external/stlport/libstlport.mk + +include $(BUILD_STATIC_LIBRARY) + +## +# filterpack_imageproc +## +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_MODULE := libfilterpack_imageproc + +LOCAL_SRC_FILES += native/imageproc/brightness.c \ + native/imageproc/contrast.c \ + native/imageproc/invert.c \ + native/imageproc/to_rgba.c + +LOCAL_SHARED_LIBRARIES := libutils libfilterfw + +LOCAL_PRELINK_MODULE := false + +include $(BUILD_SHARED_LIBRARY) + + diff --git a/media/mca/filterpacks/java/android/filterpacks/base/CallbackFilter.java b/media/mca/filterpacks/java/android/filterpacks/base/CallbackFilter.java new file mode 100644 index 0000000..4185343 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/CallbackFilter.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.os.Handler; +import android.os.Looper; + +import java.lang.Runnable; + +/** + * @hide + */ +public class CallbackFilter extends Filter { + + @GenerateFieldPort(name = "listener", hasDefault = true) + private FilterContext.OnFrameReceivedListener mListener; + + @GenerateFieldPort(name = "userData", hasDefault = true) + private Object mUserData; + + @GenerateFinalPort(name = "callUiThread", hasDefault = true) + private boolean mCallbacksOnUiThread = true; + + private Handler mUiThreadHandler; + + private class CallbackRunnable implements Runnable { + private Filter mFilter; + private Frame mFrame; + private Object mUserData; + private FilterContext.OnFrameReceivedListener mListener; + + public CallbackRunnable(FilterContext.OnFrameReceivedListener listener, Filter filter, Frame frame, Object userData) { + mListener = listener; + mFilter = filter; + mFrame = frame; + mUserData = userData; + } + + public void run() { + mListener.onFrameReceived(mFilter, mFrame, mUserData); + mFrame.release(); + } + } + + public CallbackFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addInputPort("frame"); + } + + public void prepare(FilterContext context) { + if (mCallbacksOnUiThread) { + mUiThreadHandler = new Handler(Looper.getMainLooper()); + } + } + + public void process(FilterContext context) { + // Get frame and forward to listener + final Frame input = pullInput("frame"); + if (mListener != null) { + if (mCallbacksOnUiThread) { + input.retain(); + CallbackRunnable uiRunnable = new CallbackRunnable(mListener, this, input, mUserData); + if (!mUiThreadHandler.post(uiRunnable)) { + throw new RuntimeException("Unable to send callback to UI thread!"); + } + } else { + mListener.onFrameReceived(this, input, mUserData); + } + } else { + throw new RuntimeException("CallbackFilter received frame, but no listener set!"); + } + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/FrameBranch.java b/media/mca/filterpacks/java/android/filterpacks/base/FrameBranch.java new file mode 100644 index 0000000..6b8cbc7 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/FrameBranch.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.KeyValueMap; + +/** + * @hide + */ +public class FrameBranch extends Filter { + + @GenerateFinalPort(name = "outputs", hasDefault = true) + private int mNumberOfOutputs = 2; + + public FrameBranch(String name) { + super(name); + } + + @Override + public void setupPorts() { + addInputPort("in"); + for (int i = 0; i < mNumberOfOutputs; ++i) { + addOutputBasedOnInput("out" + i, "in"); + } + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("in"); + + // Push output + for (int i = 0; i < mNumberOfOutputs; ++i) { + pushOutput("out" + i, input); + } + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/FrameFetch.java b/media/mca/filterpacks/java/android/filterpacks/base/FrameFetch.java new file mode 100644 index 0000000..518b837 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/FrameFetch.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; + +import android.util.Log; + +/** + * @hide + */ +public class FrameFetch extends Filter { + + @GenerateFinalPort(name = "format", hasDefault = true) + private FrameFormat mFormat; + + @GenerateFieldPort(name = "key") + private String mKey; + + @GenerateFieldPort(name = "repeatFrame", hasDefault = true) + private boolean mRepeatFrame = false; + + public FrameFetch(String name) { + super(name); + } + + @Override + public void setupPorts() { + addOutputPort("frame", mFormat == null ? FrameFormat.unspecified() : mFormat); + } + + public void process(FilterContext context) { + Frame output = context.fetchFrame(mKey); + if (output != null) { + pushOutput("frame", output); + if (!mRepeatFrame) { + closeOutputPort("frame"); + } + } else { + delayNextProcess(250); + } + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/FrameSource.java b/media/mca/filterpacks/java/android/filterpacks/base/FrameSource.java new file mode 100644 index 0000000..1218d1a --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/FrameSource.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; + +/** + * @hide + */ +public class FrameSource extends Filter { + + @GenerateFinalPort(name = "format") + private FrameFormat mFormat; + + @GenerateFieldPort(name = "frame", hasDefault = true) + private Frame mFrame = null; + + @GenerateFieldPort(name = "repeatFrame", hasDefault = true) + private boolean mRepeatFrame = false; + + public FrameSource(String name) { + super(name); + } + + @Override + public void setupPorts() { + addOutputPort("frame", mFormat); + } + + @Override + public void process(FilterContext context) { + if (mFrame != null) { + // Push output + pushOutput("frame", mFrame); + } + + if (!mRepeatFrame) { + // Close output port as we are done here + closeOutputPort("frame"); + } + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/FrameStore.java b/media/mca/filterpacks/java/android/filterpacks/base/FrameStore.java new file mode 100644 index 0000000..3aadaac --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/FrameStore.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; + +/** + * @hide + */ +public class FrameStore extends Filter { + + @GenerateFieldPort(name = "key") + private String mKey; + + public FrameStore(String name) { + super(name); + } + + @Override + public void setupPorts() { + addInputPort("frame"); + } + + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("frame"); + + // Store frame + context.storeFrame(mKey, input); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/GLTextureSource.java b/media/mca/filterpacks/java/android/filterpacks/base/GLTextureSource.java new file mode 100644 index 0000000..1776820 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/GLTextureSource.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GLFrame; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.format.ImageFormat; + +import java.util.Set; + +/** + * @hide + */ +public class GLTextureSource extends Filter { + + @GenerateFieldPort(name = "texId") + private int mTexId; + + @GenerateFieldPort(name = "width") + private int mWidth; + + @GenerateFieldPort(name = "height") + private int mHeight; + + @GenerateFieldPort(name = "repeatFrame", hasDefault = true) + private boolean mRepeatFrame = false; + + /* This timestamp will be used for all output frames from this source. They + * represent nanoseconds, and should be positive and monotonically + * increasing. Set to Frame.TIMESTAMP_UNKNOWN if timestamps are not + * meaningful for these textures. + */ + @GenerateFieldPort(name = "timestamp", hasDefault = true) + private long mTimestamp = Frame.TIMESTAMP_UNKNOWN; + + private Frame mFrame; + + public GLTextureSource(String name) { + super(name); + } + + @Override + public void setupPorts() { + addOutputPort("frame", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + // Release frame, so that it is recreated during the next process call + if (mFrame != null) { + mFrame.release(); + mFrame = null; + } + } + + @Override + public void process(FilterContext context) { + // Generate frame if not generated already + if (mFrame == null) { + FrameFormat outputFormat = ImageFormat.create(mWidth, mHeight, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + mFrame = context.getFrameManager().newBoundFrame(outputFormat, + GLFrame.EXISTING_TEXTURE_BINDING, + mTexId); + mFrame.setTimestamp(mTimestamp); + } + + // Push output + pushOutput("frame", mFrame); + + if (!mRepeatFrame) { + // Close output port as we are done here + closeOutputPort("frame"); + } + } + + @Override + public void tearDown(FilterContext context) { + if (mFrame != null) { + mFrame.release(); + } + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/GLTextureTarget.java b/media/mca/filterpacks/java/android/filterpacks/base/GLTextureTarget.java new file mode 100644 index 0000000..b2285cd --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/GLTextureTarget.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GLFrame; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.format.ImageFormat; + +import java.util.Set; + +/** + * @hide + */ +public class GLTextureTarget extends Filter { + + @GenerateFieldPort(name = "texId") + private int mTexId; + + public GLTextureTarget(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("frame", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("frame"); + + FrameFormat format = ImageFormat.create(input.getFormat().getWidth(), + input.getFormat().getHeight(), + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + + Frame frame = context.getFrameManager().newBoundFrame(format, GLFrame.EXISTING_TEXTURE_BINDING, mTexId); + + // Copy to our texture frame + frame.setDataFromFrame(input); + frame.release(); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/InputStreamSource.java b/media/mca/filterpacks/java/android/filterpacks/base/InputStreamSource.java new file mode 100644 index 0000000..6c22ee7 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/InputStreamSource.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.format.PrimitiveFormat; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * @hide + */ +public class InputStreamSource extends Filter { + + @GenerateFinalPort(name = "target") + private String mTarget; + + @GenerateFieldPort(name = "stream") + private InputStream mInputStream; + + @GenerateFinalPort(name = "format", hasDefault = true) + private MutableFrameFormat mOutputFormat = null; + + public InputStreamSource(String name) { + super(name); + } + + @Override + public void setupPorts() { + int target = FrameFormat.readTargetString(mTarget); + if (mOutputFormat == null) { + mOutputFormat = PrimitiveFormat.createByteFormat(target); + } + addOutputPort("data", mOutputFormat); + } + + @Override + public void process(FilterContext context) { + int fileSize = 0; + ByteBuffer byteBuffer = null; + + // Read the file + try { + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = mInputStream.read(buffer)) > 0) { + byteStream.write(buffer, 0, bytesRead); + fileSize += bytesRead; + } + byteBuffer = ByteBuffer.wrap(byteStream.toByteArray()); + } catch (IOException exception) { + throw new RuntimeException( + "InputStreamSource: Could not read stream: " + exception.getMessage() + "!"); + } + + // Put it into a frame + mOutputFormat.setDimensions(fileSize); + Frame output = context.getFrameManager().newFrame(mOutputFormat); + output.setData(byteBuffer); + + // Push output + pushOutput("data", output); + + // Release pushed frame + output.release(); + + // Close output port as we are done here + closeOutputPort("data"); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/NullFilter.java b/media/mca/filterpacks/java/android/filterpacks/base/NullFilter.java new file mode 100644 index 0000000..f3e08e4 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/NullFilter.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; + +/** + * @hide + */ +public class NullFilter extends Filter { + + public NullFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addInputPort("frame"); + } + + @Override + public void process(FilterContext context) { + pullInput("frame"); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/ObjectSource.java b/media/mca/filterpacks/java/android/filterpacks/base/ObjectSource.java new file mode 100644 index 0000000..d511e44 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/ObjectSource.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.base; + +import java.util.Set; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.format.ObjectFormat; + +/** + * @hide + */ +public class ObjectSource extends Filter { + + @GenerateFieldPort(name = "object") + private Object mObject; + + @GenerateFinalPort(name = "format", hasDefault = true) + private FrameFormat mOutputFormat = FrameFormat.unspecified(); + + @GenerateFieldPort(name = "repeatFrame", hasDefault = true) + boolean mRepeatFrame = false; + + private Frame mFrame; + + public ObjectSource(String name) { + super(name); + } + + @Override + public void setupPorts() { + addOutputPort("frame", mOutputFormat); + } + + @Override + public void process(FilterContext context) { + // If no frame has been created, create one now. + if (mFrame == null) { + if (mObject == null) { + throw new NullPointerException("ObjectSource producing frame with no object set!"); + } + FrameFormat outputFormat = ObjectFormat.fromObject(mObject, FrameFormat.TARGET_SIMPLE); + mFrame = context.getFrameManager().newFrame(outputFormat); + mFrame.setObjectValue(mObject); + mFrame.setTimestamp(Frame.TIMESTAMP_UNKNOWN); + } + + // Push output + pushOutput("frame", mFrame); + + // Wait for free output + if (!mRepeatFrame) { + closeOutputPort("frame"); + } + } + + @Override + public void tearDown(FilterContext context) { + mFrame.release(); + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + // Release our internal frame, so that it is regenerated on the next call to process(). + if (name.equals("object")) { + if (mFrame != null) { + mFrame.release(); + mFrame = null; + } + } + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/OutputStreamTarget.java b/media/mca/filterpacks/java/android/filterpacks/base/OutputStreamTarget.java new file mode 100644 index 0000000..3d3d0f1 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/OutputStreamTarget.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; + +import java.io.OutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * @hide + */ +public class OutputStreamTarget extends Filter { + + @GenerateFieldPort(name = "stream") + private OutputStream mOutputStream; + + public OutputStreamTarget(String name) { + super(name); + } + + @Override + public void setupPorts() { + addInputPort("data"); + } + + @Override + public void process(FilterContext context) { + Frame input = pullInput("data"); + ByteBuffer data; + + if (input.getFormat().getObjectClass() == String.class) { + String stringVal = (String)input.getObjectValue(); + data = ByteBuffer.wrap(stringVal.getBytes()); + } else { + data = input.getData(); + } + try { + mOutputStream.write(data.array(), 0, data.limit()); + mOutputStream.flush(); + } catch (IOException exception) { + throw new RuntimeException( + "OutputStreamTarget: Could not write to stream: " + exception.getMessage() + "!"); + } + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/RetargetFilter.java b/media/mca/filterpacks/java/android/filterpacks/base/RetargetFilter.java new file mode 100644 index 0000000..254167a --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/RetargetFilter.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; + +/** + * @hide + */ +public class RetargetFilter extends Filter { + + @GenerateFinalPort(name = "target", hasDefault = false) + private String mTargetString; + + private MutableFrameFormat mOutputFormat; + private int mTarget = -1; + + public RetargetFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + // Setup target + mTarget = FrameFormat.readTargetString(mTargetString); + + // Add ports + addInputPort("frame"); + addOutputBasedOnInput("frame", "frame"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + MutableFrameFormat retargeted = inputFormat.mutableCopy(); + retargeted.setTarget(mTarget); + return retargeted; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("frame"); + + // Create output frame + Frame output = context.getFrameManager().duplicateFrameToTarget(input, mTarget); + + // Push output + pushOutput("frame", output); + + // Release pushed frame + output.release(); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/AlphaBlendFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/AlphaBlendFilter.java new file mode 100644 index 0000000..473369c --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/AlphaBlendFilter.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import java.util.Set; + +/** + * @hide + */ +public class AlphaBlendFilter extends ImageCombineFilter { + + private final String mAlphaBlendShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform sampler2D tex_sampler_2;\n" + + "uniform float weight;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 colorL = texture2D(tex_sampler_0, v_texcoord);\n" + + " vec4 colorR = texture2D(tex_sampler_1, v_texcoord);\n" + + " float blend = texture2D(tex_sampler_2, v_texcoord).r * weight;\n" + + " gl_FragColor = colorL * (1.0 - blend) + colorR * blend;\n" + + "}\n"; + + public AlphaBlendFilter(String name) { + super(name, new String[] { "source", "overlay", "mask" }, "blended", "weight"); + } + + @Override + protected Program getNativeProgram(FilterContext context) { + throw new RuntimeException("TODO: Write native implementation for AlphaBlend!"); + } + + @Override + protected Program getShaderProgram(FilterContext context) { + return new ShaderProgram(context, mAlphaBlendShader); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/AutoFixFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/AutoFixFilter.java new file mode 100644 index 0000000..c71c1c9 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/AutoFixFilter.java @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.util.Log; + +public class AutoFixFilter extends Filter { + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + @GenerateFieldPort(name = "scale") + private float mScale; + + private static final int normal_cdf[] = { + 9, 33, 50, 64, 75, 84, 92, 99, 106, 112, 117, 122, 126, 130, 134, 138, 142, + 145, 148, 150, 154, 157, 159, 162, 164, 166, 169, 170, 173, 175, 177, 179, + 180, 182, 184, 186, 188, 189, 190, 192, 194, 195, 197, 198, 199, 200, 202, + 203, 205, 206, 207, 208, 209, 210, 212, 213, 214, 215, 216, 217, 218, 219, + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 229, 230, 231, 232, 233, + 234, 235, 236, 236, 237, 238, 239, 239, 240, 240, 242, 242, 243, 244, 245, + 245, 246, 247, 247, 248, 249, 249, 250, 250, 251, 252, 253, 253, 254, 255, + 255, 256, 256, 257, 258, 258, 259, 259, 259, 260, 261, 262, 262, 263, 263, + 264, 264, 265, 265, 266, 267, 267, 268, 268, 269, 269, 269, 270, 270, 271, + 272, 272, 273, 273, 274, 274, 275, 275, 276, 276, 277, 277, 277, 278, 278, + 279, 279, 279, 280, 280, 281, 282, 282, 282, 283, 283, 284, 284, 285, 285, + 285, 286, 286, 287, 287, 288, 288, 288, 289, 289, 289, 290, 290, 290, 291, + 292, 292, 292, 293, 293, 294, 294, 294, 295, 295, 296, 296, 296, 297, 297, + 297, 298, 298, 298, 299, 299, 299, 299, 300, 300, 301, 301, 302, 302, 302, + 303, 303, 304, 304, 304, 305, 305, 305, 306, 306, 306, 307, 307, 307, 308, + 308, 308, 309, 309, 309, 309, 310, 310, 310, 310, 311, 312, 312, 312, 313, + 313, 313, 314, 314, 314, 315, 315, 315, 315, 316, 316, 316, 317, 317, 317, + 318, 318, 318, 319, 319, 319, 319, 319, 320, 320, 320, 321, 321, 322, 322, + 322, 323, 323, 323, 323, 324, 324, 324, 325, 325, 325, 325, 326, 326, 326, + 327, 327, 327, 327, 328, 328, 328, 329, 329, 329, 329, 329, 330, 330, 330, + 330, 331, 331, 332, 332, 332, 333, 333, 333, 333, 334, 334, 334, 334, 335, + 335, 335, 336, 336, 336, 336, 337, 337, 337, 337, 338, 338, 338, 339, 339, + 339, 339, 339, 339, 340, 340, 340, 340, 341, 341, 342, 342, 342, 342, 343, + 343, 343, 344, 344, 344, 344, 345, 345, 345, 345, 346, 346, 346, 346, 347, + 347, 347, 347, 348, 348, 348, 348, 349, 349, 349, 349, 349, 349, 350, 350, + 350, 350, 351, 351, 352, 352, 352, 352, 353, 353, 353, 353, 354, 354, 354, + 354, 355, 355, 355, 355, 356, 356, 356, 356, 357, 357, 357, 357, 358, 358, + 358, 358, 359, 359, 359, 359, 359, 359, 359, 360, 360, 360, 360, 361, 361, + 362, 362, 362, 362, 363, 363, 363, 363, 364, 364, 364, 364, 365, 365, 365, + 365, 366, 366, 366, 366, 366, 367, 367, 367, 367, 368, 368, 368, 368, 369, + 369, 369, 369, 369, 369, 370, 370, 370, 370, 370, 371, 371, 372, 372, 372, + 372, 373, 373, 373, 373, 374, 374, 374, 374, 374, 375, 375, 375, 375, 376, + 376, 376, 376, 377, 377, 377, 377, 378, 378, 378, 378, 378, 379, 379, 379, + 379, 379, 379, 380, 380, 380, 380, 381, 381, 381, 382, 382, 382, 382, 383, + 383, 383, 383, 384, 384, 384, 384, 385, 385, 385, 385, 385, 386, 386, 386, + 386, 387, 387, 387, 387, 388, 388, 388, 388, 388, 389, 389, 389, 389, 389, + 389, 390, 390, 390, 390, 391, 391, 392, 392, 392, 392, 392, 393, 393, 393, + 393, 394, 394, 394, 394, 395, 395, 395, 395, 396, 396, 396, 396, 396, 397, + 397, 397, 397, 398, 398, 398, 398, 399, 399, 399, 399, 399, 399, 400, 400, + 400, 400, 400, 401, 401, 402, 402, 402, 402, 403, 403, 403, 403, 404, 404, + 404, 404, 405, 405, 405, 405, 406, 406, 406, 406, 406, 407, 407, 407, 407, + 408, 408, 408, 408, 409, 409, 409, 409, 409, 409, 410, 410, 410, 410, 411, + 411, 412, 412, 412, 412, 413, 413, 413, 413, 414, 414, 414, 414, 415, 415, + 415, 415, 416, 416, 416, 416, 417, 417, 417, 417, 418, 418, 418, 418, 419, + 419, 419, 419, 419, 419, 420, 420, 420, 420, 421, 421, 422, 422, 422, 422, + 423, 423, 423, 423, 424, 424, 424, 425, 425, 425, 425, 426, 426, 426, 426, + 427, 427, 427, 427, 428, 428, 428, 429, 429, 429, 429, 429, 429, 430, 430, + 430, 430, 431, 431, 432, 432, 432, 433, 433, 433, 433, 434, 434, 434, 435, + 435, 435, 435, 436, 436, 436, 436, 437, 437, 437, 438, 438, 438, 438, 439, + 439, 439, 439, 439, 440, 440, 440, 441, 441, 442, 442, 442, 443, 443, 443, + 443, 444, 444, 444, 445, 445, 445, 446, 446, 446, 446, 447, 447, 447, 448, + 448, 448, 449, 449, 449, 449, 449, 450, 450, 450, 451, 451, 452, 452, 452, + 453, 453, 453, 454, 454, 454, 455, 455, 455, 456, 456, 456, 457, 457, 457, + 458, 458, 458, 459, 459, 459, 459, 460, 460, 460, 461, 461, 462, 462, 462, + 463, 463, 463, 464, 464, 465, 465, 465, 466, 466, 466, 467, 467, 467, 468, + 468, 469, 469, 469, 469, 470, 470, 470, 471, 472, 472, 472, 473, 473, 474, + 474, 474, 475, 475, 476, 476, 476, 477, 477, 478, 478, 478, 479, 479, 479, + 480, 480, 480, 481, 482, 482, 483, 483, 484, 484, 484, 485, 485, 486, 486, + 487, 487, 488, 488, 488, 489, 489, 489, 490, 490, 491, 492, 492, 493, 493, + 494, 494, 495, 495, 496, 496, 497, 497, 498, 498, 499, 499, 499, 500, 501, + 502, 502, 503, 503, 504, 504, 505, 505, 506, 507, 507, 508, 508, 509, 509, + 510, 510, 511, 512, 513, 513, 514, 515, 515, 516, 517, 517, 518, 519, 519, + 519, 520, 521, 522, 523, 524, 524, 525, 526, 526, 527, 528, 529, 529, 530, + 531, 532, 533, 534, 535, 535, 536, 537, 538, 539, 539, 540, 542, 543, 544, + 545, 546, 547, 548, 549, 549, 550, 552, 553, 554, 555, 556, 558, 559, 559, + 561, 562, 564, 565, 566, 568, 569, 570, 572, 574, 575, 577, 578, 579, 582, + 583, 585, 587, 589, 590, 593, 595, 597, 599, 602, 604, 607, 609, 612, 615, + 618, 620, 624, 628, 631, 635, 639, 644, 649, 654, 659, 666, 673, 680, 690, + 700, 714 }; + + private final String mAutoFixShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform sampler2D tex_sampler_2;\n" + + "uniform float scale;\n" + + "uniform float shift_scale;\n" + + "uniform float hist_offset;\n" + + "uniform float hist_scale;\n" + + "uniform float density_offset;\n" + + "uniform float density_scale;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " const vec3 weights = vec3(0.33333, 0.33333, 0.33333);\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " float energy = dot(color.rgb, weights);\n" + + " float mask_value = energy - 0.5;\n" + + " float alpha;\n" + + " if (mask_value > 0.0) {\n" + + " alpha = (pow(2.0 * mask_value, 1.5) - 1.0) * scale + 1.0;\n" + + " } else { \n" + + " alpha = (pow(2.0 * mask_value, 2.0) - 1.0) * scale + 1.0;\n" + + " }\n" + + " float index = energy * hist_scale + hist_offset;\n" + + " vec4 temp = texture2D(tex_sampler_1, vec2(index, 0.5));\n" + + " float value = temp.g + temp.r * shift_scale;\n" + + " index = value * density_scale + density_offset;\n" + + " temp = texture2D(tex_sampler_2, vec2(index, 0.5));\n" + + " value = temp.g + temp.r * shift_scale;\n" + + " float dst_energy = energy * alpha + value * (1.0 - alpha);\n" + + " float max_energy = energy / max(color.r, max(color.g, color.b));\n" + + " if (dst_energy > max_energy) {\n" + + " dst_energy = max_energy;\n" + + " }\n" + + " if (energy == 0.0) {\n" + + " gl_FragColor = color;\n" + + " } else {\n" + + " gl_FragColor = vec4(color.rgb * dst_energy / energy, color.a);\n" + + " }\n" + + "}\n"; + + private Program mShaderProgram; + private Program mNativeProgram; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private Frame mHistFrame; + private Frame mDensityFrame; + + public AutoFixFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mAutoFixShader); + shaderProgram.setMaximumTileSize(mTileSize); + mShaderProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + private void initParameters() { + mShaderProgram.setHostValue("shift_scale", 1.0f / 256f); + mShaderProgram.setHostValue("hist_offset", 0.5f / 766f); + mShaderProgram.setHostValue("hist_scale", 765f / 766f); + mShaderProgram.setHostValue("density_offset", 0.5f / 1024f); + mShaderProgram.setHostValue("density_scale", 1023f / 1024f); + mShaderProgram.setHostValue("scale", mScale); + } + + @Override + protected void prepare(FilterContext context) { + int densityDim = 1024; + int histDim = 255 * 3 + 1; + long precision = (256l * 256l - 1l); + + int[] densityTable = new int[densityDim]; + for (int i = 0; i < densityDim; ++i) { + long temp = normal_cdf[i] * precision / histDim; + densityTable[i] = (int) temp; + } + + FrameFormat densityFormat = ImageFormat.create(densityDim, 1, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + mDensityFrame = context.getFrameManager().newFrame(densityFormat); + mDensityFrame.setInts(densityTable); + } + + @Override + public void tearDown(FilterContext context) { + if (mDensityFrame != null) { + mDensityFrame.release(); + mDensityFrame = null; + } + + if (mHistFrame != null) { + mHistFrame.release(); + mHistFrame = null; + } + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mShaderProgram != null) { + mShaderProgram.setHostValue("scale", mScale); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mShaderProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + initParameters(); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + mWidth = inputFormat.getWidth(); + mHeight = inputFormat.getHeight(); + createHistogramFrame(context, mWidth, mHeight, input.getInts()); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + Frame[] inputs = {input, mHistFrame, mDensityFrame}; + mShaderProgram.process(inputs, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void createHistogramFrame(FilterContext context, int width, int height, int[] data) { + int histDims = 255 * 3 + 1; + int[] histArray = new int[histDims]; + + float border_thickness_ratio = 0.05f; + int y_border_thickness = (int) (height * border_thickness_ratio); + int x_border_thickness = (int) (width * border_thickness_ratio); + int pixels = (width - 2 * x_border_thickness) * (height - 2 * y_border_thickness); + + float count = 0f; + for (int y = y_border_thickness; y < height - y_border_thickness; ++y) { + for (int x = x_border_thickness; x < width - x_border_thickness; ++x) { + int index = y * width + x; + int energy = (data[index] & 0xFF) + ((data[index] >> 8) & 0xFF) + + ((data[index] >> 16) & 0xFF); + histArray[energy] ++; + } + } + + for (int i = 1; i < histDims; i++) { + histArray[i] += histArray[i-1]; + } + + for (int i = 0; i < histDims; i++) { + long temp = (256 * 256 - 1l) * histArray[i] / pixels; + histArray[i] = (int) temp; + } + + FrameFormat shaderHistFormat = ImageFormat.create(histDims, 1, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + if (mHistFrame != null) + mHistFrame.release(); + + mHistFrame = context.getFrameManager().newFrame(shaderHistFormat); + mHistFrame.setInts(histArray); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapOverlayFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapOverlayFilter.java new file mode 100644 index 0000000..d4c901f --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapOverlayFilter.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.filterpacks.imageproc.ImageCombineFilter; +import android.graphics.Bitmap; + +import android.util.Log; + +/** + * @hide + */ +public class BitmapOverlayFilter extends Filter { + + @GenerateFieldPort(name = "bitmap") + private Bitmap mBitmap; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + private Frame mFrame; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mOverlayShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 original = texture2D(tex_sampler_0, v_texcoord);\n" + + " vec4 mask = texture2D(tex_sampler_1, v_texcoord);\n" + + " gl_FragColor = vec4(original.rgb * (1.0 - mask.a) + mask.rgb, 1.0);\n" + + "}\n"; + + public BitmapOverlayFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mOverlayShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter FisheyeFilter does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void tearDown(FilterContext context) { + if (mFrame != null) { + mFrame.release(); + mFrame = null; + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + mWidth = inputFormat.getWidth(); + mHeight = inputFormat.getHeight(); + + createBitmapFrame(context); + } + + // Process + Frame[] inputs = {input, mFrame}; + mProgram.process(inputs, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void createBitmapFrame(FilterContext context) { + if (mBitmap != null) { + FrameFormat format = ImageFormat.create(mBitmap.getWidth(), + mBitmap.getHeight(), + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + + if (mFrame != null) { + mFrame.release(); + } + + mFrame = context.getFrameManager().newFrame(format); + mFrame.setBitmap(mBitmap); + + mBitmap.recycle(); + mBitmap = null; + } + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapSource.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapSource.java new file mode 100644 index 0000000..978fc94 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapSource.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.imageproc; + +import android.content.Context; +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeFrame; +import android.filterfw.format.ImageFormat; +import android.graphics.Bitmap; + +/** + * @hide + */ +public class BitmapSource extends Filter { + + @GenerateFieldPort(name = "target") + String mTargetString; + + @GenerateFieldPort(name = "bitmap") + private Bitmap mBitmap; + + @GenerateFieldPort(name = "recycleBitmap", hasDefault = true) + private boolean mRecycleBitmap = true; + + @GenerateFieldPort(name = "repeatFrame", hasDefault = true) + boolean mRepeatFrame = false; + + private int mTarget; + private Frame mImageFrame; + + public BitmapSource(String name) { + super(name); + } + + + @Override + public void setupPorts() { + // Setup output format + FrameFormat outputFormat = ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_UNSPECIFIED); + + // Add output port + addOutputPort("image", outputFormat); + } + + public void loadImage(FilterContext filterContext) { + // Create frame with bitmap + mTarget = FrameFormat.readTargetString(mTargetString); + FrameFormat outputFormat = ImageFormat.create(mBitmap.getWidth(), + mBitmap.getHeight(), + ImageFormat.COLORSPACE_RGBA, + mTarget); + mImageFrame = filterContext.getFrameManager().newFrame(outputFormat); + mImageFrame.setBitmap(mBitmap); + mImageFrame.setTimestamp(Frame.TIMESTAMP_UNKNOWN); + + // Free up space used by bitmap + if (mRecycleBitmap) { + mBitmap.recycle(); + } + mBitmap = null; + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + // Clear image (to trigger reload) in case parameters have been changed + if (name.equals("bitmap") || name.equals("target")) { + if (mImageFrame != null) { + mImageFrame.release(); + mImageFrame = null; + } + } + } + + @Override + public void process(FilterContext context) { + if (mImageFrame == null) { + loadImage(context); + } + + pushOutput("image", mImageFrame); + + if (!mRepeatFrame) { + closeOutputPort("image"); + } + } + + @Override + public void tearDown(FilterContext env) { + if (mImageFrame != null) { + mImageFrame.release(); + mImageFrame = null; + } + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/BlackWhiteFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/BlackWhiteFilter.java new file mode 100644 index 0000000..a1cec01 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/BlackWhiteFilter.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import java.util.Random; + +public class BlackWhiteFilter extends Filter { + + @GenerateFieldPort(name = "black", hasDefault = true) + private float mBlack = 0f; + + @GenerateFieldPort(name = "white", hasDefault = true) + private float mWhite = 1f; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private Frame mNoiseFrame = null; + private Random mRandom; + + private final String mBlackWhiteShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform float black;\n" + + "uniform float scale;\n" + + "uniform float stepsize;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " float dither = texture2D(tex_sampler_1, v_texcoord).r;\n" + + " vec3 xform = clamp((color.rgb - black) * scale, 0.0, 1.0);\n" + + " vec3 temp = clamp((color.rgb + stepsize - black) * scale, 0.0, 1.0);\n" + + " vec3 new_color = clamp(xform + (temp - xform) * (dither - 0.5), 0.0, 1.0);\n" + + " gl_FragColor = vec4(new_color, color.a);\n" + + "}\n"; + + public BlackWhiteFilter(String name) { + super(name); + + mRandom = new Random(); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + @Override + public void tearDown(FilterContext context) { + if (mNoiseFrame != null) { + mNoiseFrame.release(); + mNoiseFrame = null; + } + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mBlackWhiteShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + updateParameters(); + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + private void updateParameters() { + float scale = (mBlack != mWhite) ? 1.0f / (mWhite - mBlack) : 2000f; + float stepsize = 1.0f / 255.0f; + + mProgram.setHostValue("black", mBlack); + mProgram.setHostValue("scale", scale); + mProgram.setHostValue("stepsize", stepsize); + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateParameters(); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + mWidth = inputFormat.getWidth(); + mHeight = inputFormat.getHeight(); + + if (mNoiseFrame != null) { + mNoiseFrame.release(); + } + + int[] buffer = new int[mWidth * mHeight]; + for (int i = 0; i < mWidth * mHeight; ++i) { + buffer[i] = mRandom.nextInt(255); + } + FrameFormat format = ImageFormat.create(mWidth, mHeight, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + mNoiseFrame = context.getFrameManager().newFrame(format); + mNoiseFrame.setInts(buffer); + } + + if (mNoiseFrame != null && (mNoiseFrame.getFormat().getWidth() != mWidth || + mNoiseFrame.getFormat().getHeight() != mHeight)) { + throw new RuntimeException("Random map and imput image size mismatch!"); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + Frame[] inputs = {input, mNoiseFrame}; + mProgram.process(inputs, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/BlendFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/BlendFilter.java new file mode 100644 index 0000000..29bc8a3 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/BlendFilter.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import java.util.Set; + +/** + * The filter linearly blends "left" and "right" frames. The blending weight is + * the multiplication of parameter "blend" and the alpha value in "right" frame. + * @hide + */ +public class BlendFilter extends ImageCombineFilter { + + private final String mBlendShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform float blend;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 colorL = texture2D(tex_sampler_0, v_texcoord);\n" + + " vec4 colorR = texture2D(tex_sampler_1, v_texcoord);\n" + + " float weight = colorR.a * blend;\n" + + " gl_FragColor = mix(colorL, colorR, weight);\n" + + "}\n"; + + public BlendFilter(String name) { + super(name, new String[] { "left", "right" }, "blended", "blend"); + } + + @Override + protected Program getNativeProgram(FilterContext context) { + throw new RuntimeException("TODO: Write native implementation for Blend!"); + } + + @Override + protected Program getShaderProgram(FilterContext context) { + return new ShaderProgram(context, mBlendShader); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/BrightnessFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/BrightnessFilter.java new file mode 100644 index 0000000..046e69d --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/BrightnessFilter.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; + +/** + * @hide + */ +public class BrightnessFilter extends SimpleImageFilter { + + private static final String mBrightnessShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform float brightness;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " gl_FragColor = brightness * color;\n" + + "}\n"; + + public BrightnessFilter(String name) { + super(name, "brightness"); + } + + @Override + protected Program getNativeProgram(FilterContext context) { + return new NativeProgram("filterpack_imageproc", "brightness"); + } + + @Override + protected Program getShaderProgram(FilterContext context) { + return new ShaderProgram(context, mBrightnessShader); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ColorTemperatureFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ColorTemperatureFilter.java new file mode 100644 index 0000000..19da006 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ColorTemperatureFilter.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.util.Log; + +public class ColorTemperatureFilter extends Filter { + + @GenerateFieldPort(name = "scale", hasDefault = true) + private float mScale = 0.5f; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mColorTemperatureShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform float scale;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " vec3 new_color = color.rgb;\n" + + " new_color.r = color.r + color.r * ( 1.0 - color.r) * scale;\n" + + " new_color.b = color.b - color.b * ( 1.0 - color.b) * scale;\n" + + " if (scale > 0.0) { \n" + + " color.g = color.g + color.g * ( 1.0 - color.g) * scale * 0.25;\n" + + " }\n" + + " float max_value = max(new_color.r, max(new_color.g, new_color.b));\n" + + " if (max_value > 1.0) { \n" + + " new_color /= max_value;\n" + + " } \n" + + " gl_FragColor = vec4(new_color, color.a);\n" + + "}\n"; + + public ColorTemperatureFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mColorTemperatureShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + updateParameters(); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void updateParameters() { + mProgram.setHostValue("scale", 2.0f * mScale - 1.0f); + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateParameters(); + } + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ContrastFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ContrastFilter.java new file mode 100644 index 0000000..70e987f --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ContrastFilter.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; + +import java.util.Set; + +/** + * @hide + */ +public class ContrastFilter extends SimpleImageFilter { + + private static final String mContrastShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform float contrast;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " color -= 0.5;\n" + + " color *= contrast;\n" + + " color += 0.5;\n" + + " gl_FragColor = color;\n" + // this will clamp + "}\n"; + + public ContrastFilter(String name) { + super(name, "contrast"); + } + + @Override + protected Program getNativeProgram(FilterContext context) { + return new NativeProgram("filterpack_imageproc", "contrast"); + } + + @Override + protected Program getShaderProgram(FilterContext context) { + return new ShaderProgram(context, mContrastShader); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/CropFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/CropFilter.java new file mode 100644 index 0000000..5222d9c --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/CropFilter.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.geometry.Point; +import android.filterfw.geometry.Quad; +import android.filterfw.format.ImageFormat; +import android.filterfw.format.ObjectFormat; + +import android.util.Log; + +/** + * @hide + */ +public class CropFilter extends Filter { + + private Program mProgram; + private FrameFormat mLastFormat = null; + + @GenerateFieldPort(name = "owidth") + private int mOutputWidth = -1; + + @GenerateFieldPort(name = "oheight") + private int mOutputHeight = -1; + + @GenerateFieldPort(name = "fillblack") + private boolean mFillBlack = false; + + public CropFilter(String name) { + super(name); + } + + private final String mFragShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " const vec2 lo = vec2(0.0, 0.0);\n" + + " const vec2 hi = vec2(1.0, 1.0);\n" + + " const vec4 black = vec4(0.0, 0.0, 0.0, 1.0);\n" + + " bool out_of_bounds =\n" + + " any(lessThan(v_texcoord, lo)) ||\n" + + " any(greaterThan(v_texcoord, hi));\n" + + " if (out_of_bounds) {\n" + + " gl_FragColor = black;\n" + + " } else {\n" + + " gl_FragColor = texture2D(tex_sampler_0, v_texcoord);\n" + + " }\n" + + "}\n"; + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addMaskedInputPort("box", ObjectFormat.fromClass(Quad.class, FrameFormat.TARGET_SIMPLE)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + // Make sure output size is set to unspecified, as we do not know what we will be resizing + // to. + MutableFrameFormat outputFormat = inputFormat.mutableCopy(); + outputFormat.setDimensions(FrameFormat.SIZE_UNSPECIFIED, FrameFormat.SIZE_UNSPECIFIED); + return outputFormat; + } + + protected void createProgram(FilterContext context, FrameFormat format) { + // TODO: Add CPU version + if (mLastFormat != null && mLastFormat.getTarget() == format.getTarget()) return; + mLastFormat = format; + mProgram = null; + switch (format.getTarget()) { + case FrameFormat.TARGET_GPU: + if(mFillBlack) + mProgram = new ShaderProgram(context, mFragShader); + else + mProgram = ShaderProgram.createIdentity(context); + + break; + } + if (mProgram == null) { + throw new RuntimeException("Could not create a program for crop filter " + this + "!"); + } + } + + @Override + public void process(FilterContext env) { + // Get input frame + Frame imageFrame = pullInput("image"); + Frame boxFrame = pullInput("box"); + + createProgram(env, imageFrame.getFormat()); + + // Get the box + Quad box = (Quad)boxFrame.getObjectValue(); + + // Create output format + MutableFrameFormat outputFormat = imageFrame.getFormat().mutableCopy(); + outputFormat.setDimensions(mOutputWidth == -1 ? outputFormat.getWidth() : mOutputWidth, + mOutputHeight == -1 ? outputFormat.getHeight() : mOutputHeight); + + // Create output frame + Frame output = env.getFrameManager().newFrame(outputFormat); + + // Set the program parameters + if (mProgram instanceof ShaderProgram) { + ShaderProgram shaderProgram = (ShaderProgram)mProgram; + shaderProgram.setSourceRegion(box); + } + + mProgram.process(imageFrame, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/CropRectFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/CropRectFilter.java new file mode 100644 index 0000000..d423d06 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/CropRectFilter.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.util.Log; + +/** + * @hide + */ +public class CropRectFilter extends Filter { + + @GenerateFieldPort(name = "xorigin") + private int mXorigin; + + @GenerateFieldPort(name = "yorigin") + private int mYorigin; + + @GenerateFieldPort(name = "width") + private int mOutputWidth; + + @GenerateFieldPort(name = "height") + private int mOutputHeight; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mWidth = 0; + private int mHeight = 0; + + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + public CropRectFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = ShaderProgram.createIdentity(context); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateSourceRect(mWidth, mHeight); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + FrameFormat outputFormat = ImageFormat.create(mOutputWidth, mOutputHeight, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + Frame output = context.getFrameManager().newFrame(outputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + updateSourceRect(inputFormat.getWidth(), inputFormat.getHeight()); + } + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + void updateSourceRect(int width, int height) { + mWidth = width; + mHeight = height; + + /* + Log.e("CropFilter", mWidth + ", " + mHeight + ", " + + (float) mXorigin / mWidth + ", " + + (float) mYorigin / mHeight + ", " + + (float) mOutputWidth / mWidth + ", " + + (float) mOutputHeight / mHeight); + */ + + ((ShaderProgram) mProgram).setSourceRect((float) mXorigin / mWidth, + (float) mYorigin / mHeight, + (float) mOutputWidth / mWidth, + (float) mOutputHeight / mHeight); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/CrossProcessFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/CrossProcessFilter.java new file mode 100644 index 0000000..e0514f8 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/CrossProcessFilter.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.util.Log; + +public class CrossProcessFilter extends Filter { + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mCrossProcessShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " vec3 ncolor = vec3(0.0, 0.0, 0.0);\n" + + " float value;\n" + + " if (color.r < 0.5) {\n" + + " value = color.r;\n" + + " } else {\n" + + " value = 1.0 - color.r;\n" + + " }\n" + + " float red = 4.0 * value * value * value;\n" + + " if (color.r < 0.5) {\n" + + " ncolor.r = red;\n" + + " } else {\n" + + " ncolor.r = 1.0 - red;\n" + + " }\n" + + " if (color.g < 0.5) {\n" + + " value = color.g;\n" + + " } else {\n" + + " value = 1.0 - color.g;\n" + + " }\n" + + " float green = 2.0 * value * value;\n" + + " if (color.g < 0.5) {\n" + + " ncolor.g = green;\n" + + " } else {\n" + + " ncolor.g = 1.0 - green;\n" + + " }\n" + + " ncolor.b = color.b * 0.5 + 0.25;\n" + + " gl_FragColor = vec4(ncolor.rgb, color.a);\n" + + "}\n"; + + public CrossProcessFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mCrossProcessShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter CrossProcess does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/DocumentaryFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/DocumentaryFilter.java new file mode 100644 index 0000000..3c7b846 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/DocumentaryFilter.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import java.util.Random; + +public class DocumentaryFilter extends Filter { + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private Frame mNoiseFrame; + private Random mRandom; + + private final String mDocumentaryShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform float stepsize;\n" + + "uniform float inv_max_dist;\n" + + "uniform vec2 center;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + // black white + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " float dither = texture2D(tex_sampler_1, v_texcoord).r;\n" + + " vec3 xform = clamp(2.0 * color.rgb, 0.0, 1.0);\n" + + " vec3 temp = clamp(2.0 * (color.rgb + stepsize), 0.0, 1.0);\n" + + " vec3 new_color = clamp(xform + (temp - xform) * (dither - 0.5), 0.0, 1.0);\n" + + // grayscale + " float gray = dot(new_color, vec3(0.299, 0.587, 0.114));\n" + + " new_color = vec3(gray, gray, gray);\n" + + // vignette + " float dist = distance(gl_FragCoord.xy, center);\n" + + " float lumen = 0.85 / (1.0 + exp((dist * inv_max_dist - 0.83) * 20.0)) + 0.15;\n" + + " gl_FragColor = vec4(new_color * lumen, color.a);\n" + + "}\n"; + + public DocumentaryFilter(String name) { + super(name); + + mRandom = new Random(); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + @Override + public void tearDown(FilterContext context) { + if (mNoiseFrame != null) { + mNoiseFrame.release(); + mNoiseFrame = null; + } + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mDocumentaryShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + mWidth = inputFormat.getWidth(); + mHeight = inputFormat.getHeight(); + + int[] buffer = new int[mWidth * mHeight]; + for (int i = 0; i < mWidth * mHeight; ++i) { + buffer[i] = mRandom.nextInt(255); + } + FrameFormat format = ImageFormat.create(mWidth, mHeight, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + if (mNoiseFrame != null) { + mNoiseFrame.release(); + } + mNoiseFrame = context.getFrameManager().newFrame(format); + mNoiseFrame.setInts(buffer); + + initParameters(); + } + + if (mNoiseFrame != null && (mNoiseFrame.getFormat().getWidth() != mWidth || + mNoiseFrame.getFormat().getHeight() != mHeight)) { + throw new RuntimeException("Random map and imput image size mismatch!"); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + Frame[] inputs = {input, mNoiseFrame}; + mProgram.process(inputs, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void initParameters() { + if (mProgram != null) { + float centerX = (float) (0.5 * mWidth); + float centerY = (float) (0.5 * mHeight); + float center[] = {centerX, centerY}; + float max_dist = (float) Math.sqrt(centerX * centerX + centerY * centerY); + + mProgram.setHostValue("center", center); + mProgram.setHostValue("inv_max_dist", 1.0f / max_dist); + mProgram.setHostValue("stepsize", 1.0f / 255.0f); + } + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawOverlayFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawOverlayFilter.java new file mode 100644 index 0000000..3f1711e --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawOverlayFilter.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GLFrame; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.geometry.Quad; +import android.filterfw.format.ImageFormat; +import android.filterfw.format.ObjectFormat; + +import android.opengl.GLES20; + +/** + * @hide + */ +public class DrawOverlayFilter extends Filter { + + private ShaderProgram mProgram; + + public DrawOverlayFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + FrameFormat imageFormatMask = ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + addMaskedInputPort("source", imageFormatMask); + addMaskedInputPort("overlay", imageFormatMask); + addMaskedInputPort("box", ObjectFormat.fromClass(Quad.class, FrameFormat.TARGET_SIMPLE)); + addOutputBasedOnInput("image", "source"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + @Override + public void prepare(FilterContext context) { + mProgram = ShaderProgram.createIdentity(context); + } + + @Override + public void process(FilterContext env) { + // Get input frame + Frame sourceFrame = pullInput("source"); + Frame overlayFrame = pullInput("overlay"); + Frame boxFrame = pullInput("box"); + + // Get the box + Quad box = (Quad)boxFrame.getObjectValue(); + box = box.translated(1.0f, 1.0f).scaled(2.0f); + + mProgram.setTargetRegion(box); + + // Create output frame with copy of input + Frame output = env.getFrameManager().newFrame(sourceFrame.getFormat()); + output.setDataFromFrame(sourceFrame); + + // Draw onto output + mProgram.process(overlayFrame, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawRectFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawRectFilter.java new file mode 100644 index 0000000..83c9348 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawRectFilter.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GLFrame; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.geometry.Quad; +import android.filterfw.format.ImageFormat; +import android.filterfw.format.ObjectFormat; + +import android.opengl.GLES20; + +/** + * @hide + */ +public class DrawRectFilter extends Filter { + + @GenerateFieldPort(name = "colorRed", hasDefault = true) + private float mColorRed = 0.8f; + + @GenerateFieldPort(name = "colorGreen", hasDefault = true) + private float mColorGreen = 0.8f; + + @GenerateFieldPort(name = "colorBlue", hasDefault = true) + private float mColorBlue = 0.0f; + + private final String mVertexShader = + "attribute vec4 aPosition;\n" + + "void main() {\n" + + " gl_Position = aPosition;\n" + + "}\n"; + + private final String mFixedColorFragmentShader = + "precision mediump float;\n" + + "uniform vec4 color;\n" + + "void main() {\n" + + " gl_FragColor = color;\n" + + "}\n"; + + private ShaderProgram mProgram; + + + public DrawRectFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + addMaskedInputPort("box", ObjectFormat.fromClass(Quad.class, FrameFormat.TARGET_SIMPLE)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + @Override + public void prepare(FilterContext context) { + mProgram = new ShaderProgram(context, mVertexShader, mFixedColorFragmentShader); + } + + @Override + public void process(FilterContext env) { + // Get input frame + Frame imageFrame = pullInput("image"); + Frame boxFrame = pullInput("box"); + + // Get the box + Quad box = (Quad)boxFrame.getObjectValue(); + box = box.scaled(2.0f).translated(-1.0f, -1.0f); + + // Create output frame with copy of input + GLFrame output = (GLFrame)env.getFrameManager().duplicateFrame(imageFrame); + + // Draw onto output + output.focus(); + renderBox(box); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void renderBox(Quad box) { + final int FLOAT_SIZE = 4; + + // Get current values + float[] color = {mColorRed, mColorGreen, mColorBlue, 1f}; + float[] vertexValues = { box.p0.x, box.p0.y, + box.p1.x, box.p1.y, + box.p3.x, box.p3.y, + box.p2.x, box.p2.y }; + + // Set the program variables + mProgram.setHostValue("color", color); + mProgram.setAttributeValues("aPosition", vertexValues, 2); + mProgram.setVertexCount(4); + + // Draw + mProgram.beginDrawing(); + GLES20.glLineWidth(1.0f); + GLES20.glDrawArrays(GLES20.GL_LINE_LOOP, 0, 4); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/DuotoneFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/DuotoneFilter.java new file mode 100644 index 0000000..d8c88ee --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/DuotoneFilter.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.graphics.Color; + +public class DuotoneFilter extends Filter { + + @GenerateFieldPort(name = "first_color", hasDefault = true) + private int mFirstColor = 0xFFFF0000; + + @GenerateFieldPort(name = "second_color", hasDefault = true) + private int mSecondColor = 0xFFFFFF00; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mDuotoneShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform vec3 first;\n" + + "uniform vec3 second;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " float energy = (color.r + color.g + color.b) * 0.3333;\n" + + " vec3 new_color = (1.0 - energy) * first + energy * second;\n" + + " gl_FragColor = vec4(new_color.rgb, color.a);\n" + + "}\n"; + + public DuotoneFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mDuotoneShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Duotone does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + updateParameters(); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void updateParameters() { + float first[] = { Color.red(mFirstColor)/255f, + Color.green(mFirstColor)/255f, + Color.blue(mFirstColor)/255f }; + float second[] = { Color.red(mSecondColor)/255f, + Color.green(mSecondColor)/255f, + Color.blue(mSecondColor)/255f }; + + mProgram.setHostValue("first", first); + mProgram.setHostValue("second", second); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/FillLightFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/FillLightFilter.java new file mode 100644 index 0000000..fc917a1 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/FillLightFilter.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.util.Log; + +public class FillLightFilter extends Filter { + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + @GenerateFieldPort(name = "strength", hasDefault = true) + private float mBacklight = 0f; + + private Program mProgram; + + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mFillLightShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform float mult;\n" + + "uniform float igamma;\n" + + "varying vec2 v_texcoord;\n" + + "void main()\n" + + "{\n" + + " const vec3 color_weights = vec3(0.25, 0.5, 0.25);\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " float lightmask = dot(color.rgb, color_weights);\n" + + " float backmask = (1.0 - lightmask);\n" + + " vec3 ones = vec3(1.0, 1.0, 1.0);\n" + + " vec3 diff = pow(mult * color.rgb, igamma * ones) - color.rgb;\n" + + " diff = min(diff, 1.0);\n" + + " vec3 new_color = min(color.rgb + diff * backmask, 1.0);\n" + + " gl_FragColor = vec4(new_color, color.a);\n" + + "}\n"; + + public FillLightFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mFillLightShader); + Log.e("FillLight", "tile size: " + mTileSize); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter FillLight does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + updateParameters(); + } + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateParameters(); + } + } + + private void updateParameters() { + float fade_gamma = 0.3f; + float amt = 1.0f - mBacklight; + float mult = 1.0f / (amt * 0.7f + 0.3f); + float faded = fade_gamma + (1.0f -fade_gamma) *mult; + float igamma = 1.0f / faded; + + mProgram.setHostValue("mult", mult); + mProgram.setHostValue("igamma", igamma); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/FisheyeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/FisheyeFilter.java new file mode 100644 index 0000000..8d38f98 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/FisheyeFilter.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.util.Log; + +import java.lang.Math; +import java.util.Set; + +/** + * @hide + */ +public class FisheyeFilter extends Filter { + private static final String TAG = "FisheyeFilter"; + + // This parameter has range between 0 and 1. It controls the effect of radial distortion. + // The larger the value, the more prominent the distortion effect becomes (a straight line + // becomes a curve). + @GenerateFieldPort(name = "scale", hasDefault = true) + private float mScale = 0f; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private static final String mFisheyeShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform vec2 center;\n" + + "uniform float alpha;\n" + + "uniform float bound;\n" + + "uniform float radius2;\n" + + "uniform float factor;\n" + + "uniform float inv_height;\n" + + "uniform float inv_width;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " const float m_pi_2 = 1.570963;\n" + + " float dist = distance(gl_FragCoord.xy, center);\n" + + " float radian = m_pi_2 - atan(alpha * sqrt(radius2 - dist * dist), dist);\n" + + " float scale = radian * factor / dist;\n" + + " vec2 new_coord = gl_FragCoord.xy * scale + (1.0 - scale) * center;\n" + + " new_coord.x *= inv_width;\n" + + " new_coord.y *= inv_height;\n" + + " vec4 color = texture2D(tex_sampler_0, new_coord);\n" + + " gl_FragColor = color;\n" + + "}\n"; + + public FisheyeFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mFisheyeShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter FisheyeFilter does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + updateFrameSize(inputFormat.getWidth(), inputFormat.getHeight()); + } + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateProgramParams(); + } + } + + private void updateFrameSize(int width, int height) { + float center[] = {0.5f * width, 0.5f * height}; + + mProgram.setHostValue("center", center); + mProgram.setHostValue("inv_width", 1.0f / width); + mProgram.setHostValue("inv_height", 1.0f / height); + + mWidth = width; + mHeight = height; + + updateProgramParams(); + } + + private void updateProgramParams() { + final float pi = 3.14159265f; + + float alpha = mScale * 2.0f + 0.75f; + float bound2 = 0.25f * (mWidth * mWidth + mHeight * mHeight); + float bound = (float) Math.sqrt(bound2); + float radius = 1.15f * bound; + float radius2 = radius * radius; + float max_radian = 0.5f * pi - + (float) Math.atan(alpha / bound * (float) Math.sqrt(radius2 - bound2)); + float factor = bound / max_radian; + + mProgram.setHostValue("radius2",radius2); + mProgram.setHostValue("factor", factor); + mProgram.setHostValue("alpha", (float) (mScale * 2.0 + 0.75)); + } + + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/FixedRotationFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/FixedRotationFilter.java new file mode 100644 index 0000000..3d319ea --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/FixedRotationFilter.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.filterfw.geometry.Point; +import android.filterfw.geometry.Quad; + +/** + * The FixedRotationFilter rotates the input image clockwise, it only accepts + * 4 rotation angles: 0, 90, 180, 270 + * @hide + */ +public class FixedRotationFilter extends Filter { + + @GenerateFieldPort(name = "rotation", hasDefault = true) + private int mRotation = 0; + + private ShaderProgram mProgram = null; + + public FixedRotationFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + @Override + public void process(FilterContext context) { + Frame input = pullInput("image"); + if (mRotation == 0) { + pushOutput("image", input); + return; + } + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null) { + mProgram = ShaderProgram.createIdentity(context); + } + MutableFrameFormat outputFormat = inputFormat.mutableCopy(); + int width = inputFormat.getWidth(); + int height = inputFormat.getHeight(); + Point p1 = new Point(0.0f, 0.0f); + Point p2 = new Point(1.0f, 0.0f); + Point p3 = new Point(0.0f, 1.0f); + Point p4 = new Point(1.0f, 1.0f); + Quad sourceRegion; + switch (((int)Math.round(mRotation / 90f)) % 4) { + case 1: + sourceRegion = new Quad(p3,p1,p4,p2); + outputFormat.setDimensions(height, width); + break; + case 2: + sourceRegion = new Quad(p4,p3,p2,p1); + break; + case 3: + sourceRegion = new Quad(p2,p4,p1,p3); + outputFormat.setDimensions(height, width); + break; + case 0: + default: + sourceRegion = new Quad(p1,p2,p3,p4); + break; + } + // Create output frame + Frame output = context.getFrameManager().newFrame(outputFormat); + + // Set the source region + mProgram.setSourceRegion(sourceRegion); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/FlipFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/FlipFilter.java new file mode 100644 index 0000000..f8b857b --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/FlipFilter.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +/** + * @hide + */ +public class FlipFilter extends Filter { + + @GenerateFieldPort(name = "vertical", hasDefault = true) + private boolean mVertical = false; + + @GenerateFieldPort(name = "horizontal", hasDefault = true) + private boolean mHorizontal = false; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + public FlipFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = ShaderProgram.createIdentity(context); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + updateParameters(); + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateParameters(); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void updateParameters() { + float x_origin = (mHorizontal) ? 1.0f : 0.0f; + float y_origin = (mVertical) ? 1.0f : 0.0f; + + float width = (mHorizontal) ? -1.0f : 1.0f; + float height = (mVertical) ? -1.0f : 1.0f; + + ((ShaderProgram) mProgram).setSourceRect(x_origin, y_origin, width, height); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/GrainFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/GrainFilter.java new file mode 100644 index 0000000..168a9c6 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/GrainFilter.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.filterfw.geometry.Quad; +import android.filterfw.geometry.Point; + +import java.util.Random; + +public class GrainFilter extends Filter { + + private static final int RAND_THRESHOLD = 128; + + @GenerateFieldPort(name = "strength", hasDefault = true) + private float mScale = 0f; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private Frame mNoiseFrame = null; + private Random mRandom; + + private final String mGrainShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform float scale;\n" + + "uniform float stepX;\n" + + "uniform float stepY;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " float noise = texture2D(tex_sampler_1, v_texcoord + vec2(-stepX, -stepY)).r * 0.224;\n" + + " noise += texture2D(tex_sampler_1, v_texcoord + vec2(-stepX, stepY)).r * 0.224;\n" + + " noise += texture2D(tex_sampler_1, v_texcoord + vec2(stepX, -stepY)).r * 0.224;\n" + + " noise += texture2D(tex_sampler_1, v_texcoord + vec2(stepX, stepY)).r * 0.224;\n" + + " noise += 0.4448;\n" + + " noise *= scale;\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " float energy = 0.33333 * color.r + 0.33333 * color.g + 0.33333 * color.b;\n" + + " float mask = (1.0 - sqrt(energy));\n" + + " float weight = 1.0 - 1.333 * mask * noise;\n" + + " gl_FragColor = vec4(color.rgb * weight, color.a);\n" + + "}\n"; + + public GrainFilter(String name) { + super(name); + + mRandom = new Random(); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mGrainShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + private void updateParameters() { + mProgram.setHostValue("scale", mScale); + } + + private void updateFrameSize(int width, int height) { + mWidth = width; + mHeight = height; + + if (mProgram != null) { + mProgram.setHostValue("stepX", 0.5f / mWidth); + mProgram.setHostValue("stepY", 0.5f / mHeight); + updateParameters(); + } + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateParameters(); + } + } + + @Override + public void tearDown(FilterContext context) { + if (mNoiseFrame != null) { + mNoiseFrame.release(); + mNoiseFrame = null; + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + updateParameters(); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + updateFrameSize(inputFormat.getWidth(), inputFormat.getHeight()); + + int[] buffer = new int[mWidth * mHeight]; + for (int i = 0; i < mWidth * mHeight; ++i) { + buffer[i] = (mRandom.nextInt(256) < RAND_THRESHOLD) ? + mRandom.nextInt(256) : 0; + } + FrameFormat format = ImageFormat.create(mWidth, mHeight, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + if (mNoiseFrame != null) { + mNoiseFrame.release(); + } + mNoiseFrame = context.getFrameManager().newFrame(format); + mNoiseFrame.setInts(buffer); + } + + if (mNoiseFrame.getFormat().getWidth() != mWidth || + mNoiseFrame.getFormat().getHeight() != mHeight) { + throw new RuntimeException("Random map and imput image size mismatch!"); + } + + // Process + Frame[] inputs = {input, mNoiseFrame}; + mProgram.process(inputs, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageCombineFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageCombineFilter.java new file mode 100644 index 0000000..858489b --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageCombineFilter.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import java.lang.reflect.Field; +import java.util.HashSet; +import java.util.Set; + +/** + * @hide + */ +public abstract class ImageCombineFilter extends Filter { + + protected Program mProgram; + protected String[] mInputNames; + protected String mOutputName; + protected String mParameterName; + protected int mCurrentTarget = FrameFormat.TARGET_UNSPECIFIED; + + public ImageCombineFilter(String name, + String[] inputNames, + String outputName, + String parameterName) { + super(name); + mInputNames = inputNames; + mOutputName = outputName; + mParameterName = parameterName; + } + + @Override + public void setupPorts() { + if (mParameterName != null) { + try { + Field programField = ImageCombineFilter.class.getDeclaredField("mProgram"); + addProgramPort(mParameterName, mParameterName, programField, float.class, false); + } catch (NoSuchFieldException e) { + throw new RuntimeException("Internal Error: mProgram field not found!"); + } + } + for (String inputName : mInputNames) { + addMaskedInputPort(inputName, ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + } + addOutputBasedOnInput(mOutputName, mInputNames[0]); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + private void assertAllInputTargetsMatch() { + int target = getInputFormat(mInputNames[0]).getTarget(); + for (String inputName : mInputNames) { + if (target != getInputFormat(inputName).getTarget()) { + throw new RuntimeException("Type mismatch of input formats in filter " + this + + ". All input frames must have the same target!"); + } + } + } + + @Override + public void process(FilterContext context) { + // Pull input frames + int i = 0; + Frame[] inputs = new Frame[mInputNames.length]; + for (String inputName : mInputNames) { + inputs[i++] = pullInput(inputName); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputs[0].getFormat()); + + // Make sure we have a program + updateProgramWithTarget(inputs[0].getFormat().getTarget(), context); + + // Process + mProgram.process(inputs, output); + + // Push output + pushOutput(mOutputName, output); + + // Release pushed frame + output.release(); + } + + protected void updateProgramWithTarget(int target, FilterContext context) { + if (target != mCurrentTarget) { + switch (target) { + case FrameFormat.TARGET_NATIVE: + mProgram = getNativeProgram(context); + break; + + case FrameFormat.TARGET_GPU: + mProgram = getShaderProgram(context); + break; + + default: + mProgram = null; + break; + } + if (mProgram == null) { + throw new RuntimeException("Could not create a program for image filter " + + this + "!"); + } + initProgramInputs(mProgram, context); + mCurrentTarget = target; + } + } + + protected abstract Program getNativeProgram(FilterContext context); + + protected abstract Program getShaderProgram(FilterContext context); +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageEncoder.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageEncoder.java new file mode 100644 index 0000000..a5405cb --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageEncoder.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.imageproc; + +import android.content.Context; +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.format.ImageFormat; +import android.graphics.Bitmap; +import android.graphics.Bitmap.CompressFormat; + +import android.util.Log; + +import java.io.OutputStream; +import java.io.IOException; + +/** + * @hide + */ +public class ImageEncoder extends Filter { + + @GenerateFieldPort(name = "stream") + private OutputStream mOutputStream; + + @GenerateFieldPort(name = "quality", hasDefault = true) + private int mQuality = 80; + + public ImageEncoder(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_UNSPECIFIED)); + } + + @Override + public void process(FilterContext env) { + Frame input = pullInput("image"); + Bitmap bitmap = input.getBitmap(); + bitmap.compress(CompressFormat.JPEG, mQuality, mOutputStream); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageSlicer.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageSlicer.java new file mode 100644 index 0000000..b996eb8 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageSlicer.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +public class ImageSlicer extends Filter { + + @GenerateFieldPort(name = "xSlices") + private int mXSlices; + + @GenerateFieldPort(name = "ySlices") + private int mYSlices; + + @GenerateFieldPort(name = "padSize") + private int mPadSize; + + // The current slice index from 0 to xSlices * ySlices + private int mSliceIndex; + + private Frame mOriginalFrame; + + private Program mProgram; + + private int mInputWidth; + private int mInputHeight; + + private int mSliceWidth; + private int mSliceHeight; + + private int mOutputWidth; + private int mOutputHeight; + + public ImageSlicer(String name) { + super(name); + mSliceIndex = 0; + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + private void calcOutputFormatForInput(Frame frame) { + + // calculate the output size based on input size, xSlices, and ySlices + mInputWidth = frame.getFormat().getWidth(); + mInputHeight = frame.getFormat().getHeight(); + + mSliceWidth = (mInputWidth + mXSlices - 1) / mXSlices; + mSliceHeight = (mInputHeight + mYSlices - 1)/ mYSlices; + + mOutputWidth = mSliceWidth + mPadSize * 2; + mOutputHeight = mSliceHeight + mPadSize * 2; + } + + + @Override + public void process(FilterContext context) { + + // Get input frame + if (mSliceIndex == 0) { + mOriginalFrame = pullInput("image"); + calcOutputFormatForInput(mOriginalFrame); + } + + FrameFormat inputFormat = mOriginalFrame.getFormat(); + MutableFrameFormat outputFormat = inputFormat.mutableCopy(); + outputFormat.setDimensions(mOutputWidth, mOutputHeight); + + // Create output frame + Frame output = context.getFrameManager().newFrame(outputFormat); + + // Create the program if not created already + if (mProgram == null) { + mProgram = ShaderProgram.createIdentity(context); + } + + // Calculate the four corner of the source region + int xSliceIndex = mSliceIndex % mXSlices; + int ySliceIndex = mSliceIndex / mXSlices; + + // TODO(rslin) : not sure shifting by 0.5 is needed. + float x0 = (xSliceIndex * mSliceWidth - mPadSize) / ((float) mInputWidth); + float y0 = (ySliceIndex * mSliceHeight - mPadSize) / ((float) mInputHeight); + + ((ShaderProgram) mProgram).setSourceRect(x0, y0, + ((float) mOutputWidth) / mInputWidth, + ((float) mOutputHeight) / mInputHeight); + + // Process + mProgram.process(mOriginalFrame, output); + mSliceIndex++; + + if (mSliceIndex == mXSlices * mYSlices) { + mSliceIndex = 0; + mOriginalFrame.release(); + setWaitsOnInputPort("image", true); + } else { + // Retain the original frame so it can be used next time. + mOriginalFrame.retain(); + setWaitsOnInputPort("image", false); + } + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageStitcher.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageStitcher.java new file mode 100644 index 0000000..20aba91 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageStitcher.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.util.Log; + +public class ImageStitcher extends Filter { + + @GenerateFieldPort(name = "xSlices") + private int mXSlices; + + @GenerateFieldPort(name = "ySlices") + private int mYSlices; + + @GenerateFieldPort(name = "padSize") + private int mPadSize; + + private Program mProgram; + private Frame mOutputFrame; + + private int mInputWidth; + private int mInputHeight; + + private int mImageWidth; + private int mImageHeight; + + private int mSliceWidth; + private int mSliceHeight; + + private int mSliceIndex; + + public ImageStitcher(String name) { + super(name); + mSliceIndex = 0; + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + private FrameFormat calcOutputFormatForInput(FrameFormat format) { + MutableFrameFormat outputFormat = format.mutableCopy(); + + mInputWidth = format.getWidth(); + mInputHeight = format.getHeight(); + + mSliceWidth = mInputWidth - 2 * mPadSize; + mSliceHeight = mInputHeight - 2 * mPadSize; + + mImageWidth = mSliceWidth * mXSlices; + mImageHeight = mSliceHeight * mYSlices; + + outputFormat.setDimensions(mImageWidth, mImageHeight); + return outputFormat; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat format = input.getFormat(); + + // Create output frame + if (mSliceIndex == 0) { + mOutputFrame = context.getFrameManager().newFrame(calcOutputFormatForInput(format)); + } else { + if ((format.getWidth() != mInputWidth) || + (format.getHeight() != mInputHeight)) { + // CHECK input format here + throw new RuntimeException("Image size should not change."); + } + } + + // Create the program if not created already + if (mProgram == null) { + mProgram = ShaderProgram.createIdentity(context); + } + + // TODO(rslin) : not sure shifting by 0.5 is needed. + float x0 = ((float) mPadSize) / mInputWidth; + float y0 = ((float) mPadSize) / mInputHeight; + + int outputOffsetX = (mSliceIndex % mXSlices) * mSliceWidth; + int outputOffsetY = (mSliceIndex / mXSlices) * mSliceHeight; + + float outputWidth = (float) Math.min(mSliceWidth, mImageWidth - outputOffsetX); + float outputHeight = (float) Math.min(mSliceHeight, mImageHeight - outputOffsetY); + + // We need to set the source rect as well because the input are padded images. + ((ShaderProgram) mProgram).setSourceRect(x0, y0, + outputWidth / mInputWidth, + outputHeight / mInputHeight); + + ((ShaderProgram) mProgram).setTargetRect(((float) outputOffsetX)/ mImageWidth, + ((float) outputOffsetY) / mImageHeight, + outputWidth / mImageWidth, + outputHeight / mImageHeight); + + // Process this tile + mProgram.process(input, mOutputFrame); + mSliceIndex++; + + // Push output + if (mSliceIndex == mXSlices * mYSlices) { + pushOutput("image", mOutputFrame); + mOutputFrame.release(); + mSliceIndex = 0; + } + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/Invert.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/Invert.java new file mode 100644 index 0000000..400fd5d --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/Invert.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; + +/** + * @hide + */ +public class Invert extends SimpleImageFilter { + + private static final String mInvertShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " gl_FragColor.r = 1.0 - color.r;\n" + + " gl_FragColor.g = 1.0 - color.g;\n" + + " gl_FragColor.b = 1.0 - color.b;\n" + + " gl_FragColor.a = color.a;\n" + + "}\n"; + + public Invert(String name) { + super(name, null); + } + + @Override + protected Program getNativeProgram(FilterContext context) { + return new NativeProgram("filterpack_imageproc", "invert"); + } + + @Override + protected Program getShaderProgram(FilterContext context) { + return new ShaderProgram(context, mInvertShader); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/LomoishFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/LomoishFilter.java new file mode 100644 index 0000000..452a833 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/LomoishFilter.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import java.util.Random; + +public class LomoishFilter extends Filter { + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private Frame mNoiseFrame; + private Random mRandom; + + private final String mLomoishShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform float stepsizeX;\n" + + "uniform float stepsizeY;\n" + + "uniform float stepsize;\n" + + "uniform vec2 center;\n" + + "uniform float inv_max_dist;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + // sharpen + " vec3 nbr_color = vec3(0.0, 0.0, 0.0);\n" + + " vec2 coord;\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " coord.x = v_texcoord.x - 0.5 * stepsizeX;\n" + + " coord.y = v_texcoord.y - stepsizeY;\n" + + " nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" + + " coord.x = v_texcoord.x - stepsizeX;\n" + + " coord.y = v_texcoord.y + 0.5 * stepsizeY;\n" + + " nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" + + " coord.x = v_texcoord.x + stepsizeX;\n" + + " coord.y = v_texcoord.y - 0.5 * stepsizeY;\n" + + " nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" + + " coord.x = v_texcoord.x + stepsizeX;\n" + + " coord.y = v_texcoord.y + 0.5 * stepsizeY;\n" + + " nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" + + " vec3 s_color = vec3(color.rgb + 0.3 * nbr_color);\n" + + // cross process + " vec3 c_color = vec3(0.0, 0.0, 0.0);\n" + + " float value;\n" + + " if (s_color.r < 0.5) {\n" + + " value = s_color.r;\n" + + " } else {\n" + + " value = 1.0 - s_color.r;\n" + + " }\n" + + " float red = 4.0 * value * value * value;\n" + + " if (s_color.r < 0.5) {\n" + + " c_color.r = red;\n" + + " } else {\n" + + " c_color.r = 1.0 - red;\n" + + " }\n" + + " if (s_color.g < 0.5) {\n" + + " value = s_color.g;\n" + + " } else {\n" + + " value = 1.0 - s_color.g;\n" + + " }\n" + + " float green = 2.0 * value * value;\n" + + " if (s_color.g < 0.5) {\n" + + " c_color.g = green;\n" + + " } else {\n" + + " c_color.g = 1.0 - green;\n" + + " }\n" + + " c_color.b = s_color.b * 0.5 + 0.25;\n" + + // blackwhite + " float dither = texture2D(tex_sampler_1, v_texcoord).r;\n" + + " vec3 xform = clamp((c_color.rgb - 0.15) * 1.53846, 0.0, 1.0);\n" + + " vec3 temp = clamp((color.rgb + stepsize - 0.15) * 1.53846, 0.0, 1.0);\n" + + " vec3 bw_color = clamp(xform + (temp - xform) * (dither - 0.5), 0.0, 1.0);\n" + + // vignette + " float dist = distance(gl_FragCoord.xy, center);\n" + + " float lumen = 0.85 / (1.0 + exp((dist * inv_max_dist - 0.73) * 20.0)) + 0.15;\n" + + " gl_FragColor = vec4(bw_color * lumen, color.a);\n" + + "}\n"; + + public LomoishFilter(String name) { + super(name); + + mRandom = new Random(); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + @Override + public void tearDown(FilterContext context) { + if (mNoiseFrame != null) { + mNoiseFrame.release(); + mNoiseFrame = null; + } + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mLomoishShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + private void initParameters() { + if (mProgram !=null) { + float centerX = (float) (0.5 * mWidth); + float centerY = (float) (0.5 * mHeight); + float center[] = {centerX, centerY}; + float max_dist = (float) Math.sqrt(centerX * centerX + centerY * centerY); + + mProgram.setHostValue("center", center); + mProgram.setHostValue("inv_max_dist", 1.0f / max_dist); + + mProgram.setHostValue("stepsize", 1.0f / 255.0f); + mProgram.setHostValue("stepsizeX", 1.0f / mWidth); + mProgram.setHostValue("stepsizeY", 1.0f / mHeight); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + mWidth = inputFormat.getWidth(); + mHeight = inputFormat.getHeight(); + + int[] buffer = new int[mWidth * mHeight]; + for (int i = 0; i < mWidth * mHeight; ++i) { + buffer[i] = mRandom.nextInt(255); + } + FrameFormat format = ImageFormat.create(mWidth, mHeight, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + if (mNoiseFrame != null) { + mNoiseFrame.release(); + } + mNoiseFrame = context.getFrameManager().newFrame(format); + mNoiseFrame.setInts(buffer); + + initParameters(); + } + + if (mNoiseFrame != null && (mNoiseFrame.getFormat().getWidth() != mWidth || + mNoiseFrame.getFormat().getHeight() != mHeight)) { + throw new RuntimeException("Random map and imput image size mismatch!"); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + Frame[] inputs = {input, mNoiseFrame}; + mProgram.process(inputs, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/NegativeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/NegativeFilter.java new file mode 100644 index 0000000..440d6a6 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/NegativeFilter.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + + +public class NegativeFilter extends Filter { + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mNegativeShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " gl_FragColor = vec4(1.0 - color.rgb, color.a);\n" + + "}\n"; + + public NegativeFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mNegativeShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/PosterizeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/PosterizeFilter.java new file mode 100644 index 0000000..bc2e553 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/PosterizeFilter.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +public class PosterizeFilter extends Filter { + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mPosterizeShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " vec3 pcolor;\n" + + " pcolor.r = (color.r >= 0.5) ? 0.75 : 0.25;\n" + + " pcolor.g = (color.g >= 0.5) ? 0.75 : 0.25;\n" + + " pcolor.b = (color.b >= 0.5) ? 0.75 : 0.25;\n" + + " gl_FragColor = vec4(pcolor, color.a);\n" + + "}\n"; + + public PosterizeFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mPosterizeShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/RedEyeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/RedEyeFilter.java new file mode 100644 index 0000000..5632a5e --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/RedEyeFilter.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PointF; +import android.util.Log; + +/** + * @hide + */ +public class RedEyeFilter extends Filter { + + private static final float RADIUS_RATIO = 0.06f; + private static final float MIN_RADIUS = 10.0f; + private static final float DEFAULT_RED_INTENSITY = 1.30f; + + @GenerateFieldPort(name = "centers") + private float[] mCenters; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Frame mRedEyeFrame; + private Bitmap mRedEyeBitmap; + + private final Canvas mCanvas = new Canvas(); + private final Paint mPaint = new Paint(); + + private float mRadius; + + private int mWidth = 0; + private int mHeight = 0; + + private Program mProgram; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mRedEyeShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform float intensity;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " vec4 mask = texture2D(tex_sampler_1, v_texcoord);\n" + + " gl_FragColor = vec4(mask.a, mask.a, mask.a, 1.0) * intensity + color * (1.0 - intensity);\n" + + " if (mask.a > 0.0) {\n" + + " gl_FragColor.r = 0.0;\n" + + " float green_blue = color.g + color.b;\n" + + " float red_intensity = color.r / green_blue;\n" + + " if (red_intensity > intensity) {\n" + + " color.r = 0.5 * green_blue;\n" + + " }\n" + + " }\n" + + " gl_FragColor = color;\n" + + "}\n"; + + public RedEyeFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mRedEyeShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter RedEye does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void tearDown(FilterContext context) { + if (mRedEyeBitmap != null) { + mRedEyeBitmap.recycle(); + mRedEyeBitmap = null; + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + mWidth = inputFormat.getWidth(); + mHeight = inputFormat.getHeight(); + + createRedEyeBitmap(); + } + + createRedEyeFrame(context); + + // Process + Frame[] inputs = {input, mRedEyeFrame}; + mProgram.process(inputs, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + + // Release unused frame + mRedEyeFrame.release(); + mRedEyeFrame = null; + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateProgramParams(); + } + } + + private void createRedEyeBitmap() { + if (mRedEyeBitmap != null) { + mRedEyeBitmap.recycle(); + } + + int bitmapWidth = mWidth / 2; + int bitmapHeight = mHeight / 2; + + mRedEyeBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); + mCanvas.setBitmap(mRedEyeBitmap); + mPaint.setColor(Color.WHITE); + mRadius = Math.max(MIN_RADIUS, RADIUS_RATIO * Math.min(bitmapWidth, bitmapHeight)); + + updateProgramParams(); + } + + private void createRedEyeFrame(FilterContext context) { + FrameFormat format = ImageFormat.create(mRedEyeBitmap.getWidth() , + mRedEyeBitmap.getHeight(), + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + mRedEyeFrame = context.getFrameManager().newFrame(format); + mRedEyeFrame.setBitmap(mRedEyeBitmap); + } + + private void updateProgramParams() { + mProgram.setHostValue("intensity", DEFAULT_RED_INTENSITY); + + if ( mCenters.length % 2 == 1) { + throw new RuntimeException("The size of center array must be even."); + } + + if (mRedEyeBitmap != null) { + for (int i = 0; i < mCenters.length; i += 2) { + mCanvas.drawCircle(mCenters[i] * mRedEyeBitmap.getWidth(), + mCenters[i + 1] * mRedEyeBitmap.getHeight(), + mRadius, mPaint); + } + } + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ResizeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ResizeFilter.java new file mode 100644 index 0000000..411e061 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ResizeFilter.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GLFrame; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.opengl.GLES20; + +/** + * @hide + */ +public class ResizeFilter extends Filter { + + @GenerateFieldPort(name = "owidth") + private int mOWidth; + @GenerateFieldPort(name = "oheight") + private int mOHeight; + @GenerateFieldPort(name = "keepAspectRatio", hasDefault = true) + private boolean mKeepAspectRatio = false; + @GenerateFieldPort(name = "generateMipMap", hasDefault = true) + private boolean mGenerateMipMap = false; + + private Program mProgram; + private FrameFormat mLastFormat = null; + + private MutableFrameFormat mOutputFormat; + private int mInputChannels; + + public ResizeFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + protected void createProgram(FilterContext context, FrameFormat format) { + if (mLastFormat != null && mLastFormat.getTarget() == format.getTarget()) return; + mLastFormat = format; + switch (format.getTarget()) { + case FrameFormat.TARGET_NATIVE: + throw new RuntimeException("Native ResizeFilter not implemented yet!"); + + + case FrameFormat.TARGET_GPU: + ShaderProgram prog = ShaderProgram.createIdentity(context); + mProgram = prog; + break; + + default: + throw new RuntimeException("ResizeFilter could not create suitable program!"); + } + } + @Override + public void process(FilterContext env) { + // Get input frame + Frame input = pullInput("image"); + createProgram(env, input.getFormat()); + + // Create output frame + MutableFrameFormat outputFormat = input.getFormat().mutableCopy(); + if (mKeepAspectRatio) { + FrameFormat inputFormat = input.getFormat(); + mOHeight = mOWidth * inputFormat.getHeight() / inputFormat.getWidth(); + } + outputFormat.setDimensions(mOWidth, mOHeight); + Frame output = env.getFrameManager().newFrame(outputFormat); + + // Process + if (mGenerateMipMap) { + GLFrame mipmapped = (GLFrame)env.getFrameManager().newFrame(input.getFormat()); + mipmapped.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR_MIPMAP_NEAREST); + mipmapped.setDataFromFrame(input); + mipmapped.generateMipMap(); + mProgram.process(mipmapped, output); + mipmapped.release(); + } else { + mProgram.process(input, output); + } + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/RotateFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/RotateFilter.java new file mode 100644 index 0000000..3da7939 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/RotateFilter.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.filterfw.geometry.Quad; +import android.filterfw.geometry.Point; +import android.util.Log; + +/** + * @hide + */ +public class RotateFilter extends Filter { + + @GenerateFieldPort(name = "angle") + private int mAngle; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private int mOutputWidth; + private int mOutputHeight; + + public RotateFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = ShaderProgram.createIdentity(context); + shaderProgram.setMaximumTileSize(mTileSize); + shaderProgram.setClearsOutput(true); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateParameters(); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + mWidth = inputFormat.getWidth(); + mHeight = inputFormat.getHeight(); + mOutputWidth = mWidth; + mOutputHeight = mHeight; + + updateParameters(); + } + + // Create output frame + FrameFormat outputFormat = ImageFormat.create(mOutputWidth, mOutputHeight, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + + Frame output = context.getFrameManager().newFrame(outputFormat); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void updateParameters() { + float sinTheta; + float cosTheta; + + if (mAngle % 90 == 0) { + if (mAngle % 180 == 0) { + sinTheta = 0f; + cosTheta = (mAngle % 360 == 0) ? 1f:-1f; + } else { + cosTheta = 0f; + sinTheta = ((mAngle + 90) % 360 == 0) ? -1f:1f; + + mOutputWidth = mHeight; + mOutputHeight = mWidth; + } + } else { + throw new RuntimeException("degree has to be multiply of 90."); + } + + Point x0 = new Point(0.5f * (-cosTheta + sinTheta + 1f), + 0.5f * (-sinTheta - cosTheta + 1f)); + Point x1 = new Point(0.5f * (cosTheta + sinTheta + 1f), + 0.5f * (sinTheta - cosTheta + 1f)); + Point x2 = new Point(0.5f * (-cosTheta - sinTheta + 1f), + 0.5f * (-sinTheta + cosTheta + 1f)); + Point x3 = new Point(0.5f * (cosTheta - sinTheta + 1f), + 0.5f * (sinTheta + cosTheta + 1f)); + Quad quad = new Quad(x0, x1, x2, x3); + ((ShaderProgram) mProgram).setTargetRegion(quad); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/SaturateFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/SaturateFilter.java new file mode 100644 index 0000000..b83af39 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/SaturateFilter.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +public class SaturateFilter extends Filter { + + @GenerateFieldPort(name = "scale", hasDefault = true) + private float mScale = 0f; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mBenProgram; + private Program mHerfProgram; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mBenSaturateShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform float scale;\n" + + "uniform float shift;\n" + + "uniform vec3 weights;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " float kv = dot(color.rgb, weights) + shift;\n" + + " vec3 new_color = scale * color.rgb + (1.0 - scale) * kv;\n" + + " gl_FragColor = vec4(new_color, color.a);\n" + + "}\n"; + + private final String mHerfSaturateShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform vec3 weights;\n" + + "uniform vec3 exponents;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " float de = dot(color.rgb, weights);\n" + + " float inv_de = 1.0 / de;\n" + + " vec3 new_color = de * pow(color.rgb * inv_de, exponents);\n" + + " float max_color = max(max(max(new_color.r, new_color.g), new_color.b), 1.0);\n" + + " gl_FragColor = vec4(new_color / max_color, color.a);\n" + + "}\n"; + + + public SaturateFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mBenSaturateShader); + shaderProgram.setMaximumTileSize(mTileSize); + mBenProgram = shaderProgram; + + shaderProgram = new ShaderProgram(context, mHerfSaturateShader); + shaderProgram.setMaximumTileSize(mTileSize); + mHerfProgram = shaderProgram; + + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mBenProgram != null && mHerfProgram != null) { + updateParameters(); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mBenProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + initParameters(); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + if (mScale > 0.0f) { + mHerfProgram.process(input, output); + } else { + mBenProgram.process(input, output); + } + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void initParameters() { + float shift = 1.0f / 255.0f; + float weights[] = { 2f/8f, 5f/8f, 1f/8f}; + + mBenProgram.setHostValue("weights", weights); + mBenProgram.setHostValue("shift", shift); + + mHerfProgram.setHostValue("weights", weights); + + updateParameters(); + } + + private void updateParameters() { + + if (mScale > 0.0f) { + float exponents[] = new float[3]; + + exponents[0] = (0.9f * mScale) + 1.0f; + exponents[1] = (2.1f * mScale) + 1.0f; + exponents[2] = (2.7f * mScale) + 1.0f; + + mHerfProgram.setHostValue("exponents", exponents); + } else { + mBenProgram.setHostValue("scale", 1.0f + mScale); + } + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/SepiaFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/SepiaFilter.java new file mode 100644 index 0000000..7a83fdf --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/SepiaFilter.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +public class SepiaFilter extends Filter { + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mSepiaShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform mat3 matrix;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " vec3 new_color = min(matrix * color.rgb, 1.0);\n" + + " gl_FragColor = vec4(new_color.rgb, color.a);\n" + + "}\n"; + + public SepiaFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mSepiaShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + initParameters(); + } + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void initParameters() { + float weights[] = { 805.0f / 2048.0f, 715.0f / 2048.0f, 557.0f / 2048.0f, + 1575.0f / 2048.0f, 1405.0f / 2048.0f, 1097.0f / 2048.0f, + 387.0f / 2048.0f, 344.0f / 2048.0f, 268.0f / 2048.0f }; + mProgram.setHostValue("matrix", weights); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/SharpenFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/SharpenFilter.java new file mode 100644 index 0000000..256b769 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/SharpenFilter.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import java.util.Set; + +public class SharpenFilter extends Filter { + + @GenerateFieldPort(name = "scale", hasDefault = true) + private float mScale = 0f; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mSharpenShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform float scale;\n" + + "uniform float stepsizeX;\n" + + "uniform float stepsizeY;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec3 nbr_color = vec3(0.0, 0.0, 0.0);\n" + + " vec2 coord;\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " coord.x = v_texcoord.x - 0.5 * stepsizeX;\n" + + " coord.y = v_texcoord.y - stepsizeY;\n" + + " nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" + + " coord.x = v_texcoord.x - stepsizeX;\n" + + " coord.y = v_texcoord.y + 0.5 * stepsizeY;\n" + + " nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" + + " coord.x = v_texcoord.x + stepsizeX;\n" + + " coord.y = v_texcoord.y - 0.5 * stepsizeY;\n" + + " nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" + + " coord.x = v_texcoord.x + stepsizeX;\n" + + " coord.y = v_texcoord.y + 0.5 * stepsizeY;\n" + + " nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" + + " gl_FragColor = vec4(color.rgb - 2.0 * scale * nbr_color, color.a);\n" + + "}\n"; + + public SharpenFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mSharpenShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + updateFrameSize(inputFormat.getWidth(), inputFormat.getHeight()); + } + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void updateFrameSize(int width, int height) { + mWidth = width; + mHeight = height; + + if (mProgram != null) { + mProgram.setHostValue("stepsizeX", 1.0f / mWidth); + mProgram.setHostValue("stepsizeY", 1.0f / mHeight); + updateParameters(); + } + } + + private void updateParameters() { + mProgram.setHostValue("scale", mScale); + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateParameters(); + } + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/SimpleImageFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/SimpleImageFilter.java new file mode 100644 index 0000000..f4fc271 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/SimpleImageFilter.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import java.lang.reflect.Field; +import java.util.HashSet; +import java.util.Set; + +/** + * @hide + */ +public abstract class SimpleImageFilter extends Filter { + + protected int mCurrentTarget = FrameFormat.TARGET_UNSPECIFIED; + protected Program mProgram; + protected String mParameterName; + + public SimpleImageFilter(String name, String parameterName) { + super(name); + mParameterName = parameterName; + } + + @Override + public void setupPorts() { + if (mParameterName != null) { + try { + Field programField = SimpleImageFilter.class.getDeclaredField("mProgram"); + addProgramPort(mParameterName, mParameterName, programField, float.class, false); + } catch (NoSuchFieldException e) { + throw new RuntimeException("Internal Error: mProgram field not found!"); + } + } + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + updateProgramWithTarget(inputFormat.getTarget(), context); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + protected void updateProgramWithTarget(int target, FilterContext context) { + if (target != mCurrentTarget) { + switch (target) { + case FrameFormat.TARGET_NATIVE: + mProgram = getNativeProgram(context); + break; + + case FrameFormat.TARGET_GPU: + mProgram = getShaderProgram(context); + break; + + default: + mProgram = null; + break; + } + if (mProgram == null) { + throw new RuntimeException("Could not create a program for image filter " + this + "!"); + } + initProgramInputs(mProgram, context); + mCurrentTarget = target; + } + } + + protected abstract Program getNativeProgram(FilterContext context); + + protected abstract Program getShaderProgram(FilterContext context); +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/StraightenFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/StraightenFilter.java new file mode 100644 index 0000000..c9f097d --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/StraightenFilter.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.filterfw.geometry.Quad; +import android.filterfw.geometry.Point; +import android.util.Log; + +/** + * @hide + */ +public class StraightenFilter extends Filter { + + @GenerateFieldPort(name = "angle", hasDefault = true) + private float mAngle = 0f; + + @GenerateFieldPort(name = "maxAngle", hasDefault = true) + private float mMaxAngle = 45f; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private static final float DEGREE_TO_RADIAN = (float) Math.PI / 180.0f; + + public StraightenFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = ShaderProgram.createIdentity(context); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateParameters(); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Create output frame + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + mWidth = inputFormat.getWidth(); + mHeight = inputFormat.getHeight(); + updateParameters(); + } + + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void updateParameters() { + float cosTheta = (float) Math.cos(mAngle * DEGREE_TO_RADIAN); + float sinTheta = (float) Math.sin(mAngle * DEGREE_TO_RADIAN); + + if (mMaxAngle <= 0) + throw new RuntimeException("Max angle is out of range (0-180)."); + mMaxAngle = (mMaxAngle > 90) ? 90 : mMaxAngle; + + Point p0 = new Point(-cosTheta * mWidth + sinTheta * mHeight, + -sinTheta * mWidth - cosTheta * mHeight); + + Point p1 = new Point(cosTheta * mWidth + sinTheta * mHeight, + sinTheta * mWidth - cosTheta * mHeight); + + Point p2 = new Point(-cosTheta * mWidth - sinTheta * mHeight, + -sinTheta * mWidth + cosTheta * mHeight); + + Point p3 = new Point(cosTheta * mWidth - sinTheta * mHeight, + sinTheta * mWidth + cosTheta * mHeight); + + float maxWidth = (float) Math.max(Math.abs(p0.x), Math.abs(p1.x)); + float maxHeight = (float) Math.max(Math.abs(p0.y), Math.abs(p1.y)); + + float scale = 0.5f * Math.min( mWidth / maxWidth, + mHeight / maxHeight); + + p0.set(scale * p0.x / mWidth + 0.5f, scale * p0.y / mHeight + 0.5f); + p1.set(scale * p1.x / mWidth + 0.5f, scale * p1.y / mHeight + 0.5f); + p2.set(scale * p2.x / mWidth + 0.5f, scale * p2.y / mHeight + 0.5f); + p3.set(scale * p3.x / mWidth + 0.5f, scale * p3.y / mHeight + 0.5f); + + Quad quad = new Quad(p0, p1, p2, p3); + ((ShaderProgram) mProgram).setSourceRegion(quad); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/TintFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/TintFilter.java new file mode 100644 index 0000000..0da54a5 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/TintFilter.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.graphics.Color; + +public class TintFilter extends Filter { + + @GenerateFieldPort(name = "tint", hasDefault = true) + private int mTint = 0xFF0000FF; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mTintShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform vec3 tint;\n" + + "uniform vec3 color_ratio;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " float avg_color = dot(color_ratio, color.rgb);\n" + + " vec3 new_color = min(0.8 * avg_color + 0.2 * tint, 1.0);\n" + + " gl_FragColor = vec4(new_color.rgb, color.a);\n" + + "}\n"; + + public TintFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mTintShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateParameters(); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + initParameters(); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void initParameters() { + float color_ratio[] = {0.21f, 0.71f, 0.07f}; + mProgram.setHostValue("color_ratio", color_ratio); + + updateParameters(); + } + + private void updateParameters() { + float tint_color[] = {Color.red(mTint) / 255f, + Color.green(mTint) / 255f, + Color.blue(mTint) / 255f }; + + mProgram.setHostValue("tint", tint_color); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ToGrayFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToGrayFilter.java new file mode 100644 index 0000000..00e7bf4 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToGrayFilter.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.util.Log; + +import java.lang.reflect.Field; + +/** + * @hide + */ +public class ToGrayFilter extends SimpleImageFilter { + + @GenerateFieldPort(name = "invertSource", hasDefault = true) + private boolean mInvertSource = false; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private MutableFrameFormat mOutputFormat; + + private static final String mColorToGray4Shader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " float y = dot(color, vec4(0.299, 0.587, 0.114, 0));\n" + + " gl_FragColor = vec4(y, y, y, color.a);\n" + + "}\n"; + + public ToGrayFilter(String name) { + super(name, null); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + addOutputBasedOnInput("image", "image"); + } + + @Override + protected Program getNativeProgram(FilterContext context) { + throw new RuntimeException("Native toGray not implemented yet!"); + } + + @Override + protected Program getShaderProgram(FilterContext context) { + int inputChannels = getInputFormat("image").getBytesPerSample(); + if (inputChannels != 4) { + throw new RuntimeException("Unsupported GL input channels: " + + inputChannels + "! Channels must be 4!"); + } + ShaderProgram program = new ShaderProgram(context, mColorToGray4Shader); + program.setMaximumTileSize(mTileSize); + if (mInvertSource) + program.setSourceRect(0.0f, 1.0f, 1.0f, -1.0f); + return program; + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ToPackedGrayFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToPackedGrayFilter.java new file mode 100644 index 0000000..bc4a65e --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToPackedGrayFilter.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.util.Log; + +import java.lang.Math; +/** + * @hide + */ +public class ToPackedGrayFilter extends Filter { + + @GenerateFieldPort(name = "owidth", hasDefault = true) + private int mOWidth = FrameFormat.SIZE_UNSPECIFIED; + @GenerateFieldPort(name = "oheight", hasDefault = true) + private int mOHeight = FrameFormat.SIZE_UNSPECIFIED; + @GenerateFieldPort(name = "keepAspectRatio", hasDefault = true) + private boolean mKeepAspectRatio = false; + + private Program mProgram; + + private final String mColorToPackedGrayShader = + "precision mediump float;\n" + + "const vec4 coeff_y = vec4(0.299, 0.587, 0.114, 0);\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform float pix_stride;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " for (int i = 0; i < 4; ++i) {\n" + + " vec4 p = texture2D(tex_sampler_0,\n" + + " v_texcoord + vec2(pix_stride * float(i), 0.0));\n" + + " gl_FragColor[i] = dot(p, coeff_y);\n" + + " }\n" + + "}\n"; + + public ToPackedGrayFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return convertInputFormat(inputFormat); + } + + private void checkOutputDimensions(int outputWidth, int outputHeight) { + if (outputWidth <= 0 || outputHeight <= 0) { + throw new RuntimeException("Invalid output dimensions: " + + outputWidth + " " + outputHeight); + } + } + + private FrameFormat convertInputFormat(FrameFormat inputFormat) { + int ow = mOWidth; + int oh = mOHeight; + int w = inputFormat.getWidth(); + int h = inputFormat.getHeight(); + if (mOWidth == FrameFormat.SIZE_UNSPECIFIED) { + ow = w; + } + if (mOHeight == FrameFormat.SIZE_UNSPECIFIED) { + oh = h; + } + if (mKeepAspectRatio) { + // if keep aspect ratio, use the bigger dimension to determine the + // final output size + if (w > h) { + ow = Math.max(ow, oh); + oh = ow * h / w; + } else { + oh = Math.max(ow, oh); + ow = oh * w / h; + } + } + ow = (ow > 0 && ow < 4) ? 4 : (ow / 4) * 4; // ensure width is multiple of 4 + return ImageFormat.create(ow, oh, + ImageFormat.COLORSPACE_GRAY, + FrameFormat.TARGET_NATIVE); + } + + @Override + public void prepare(FilterContext context) { + mProgram = new ShaderProgram(context, mColorToPackedGrayShader); + } + + @Override + public void process(FilterContext context) { + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + FrameFormat outputFormat = convertInputFormat(inputFormat); + int ow = outputFormat.getWidth(); + int oh = outputFormat.getHeight(); + checkOutputDimensions(ow, oh); + mProgram.setHostValue("pix_stride", 1.0f / ow); + + // Do the RGBA to luminance conversion. + MutableFrameFormat tempFrameFormat = inputFormat.mutableCopy(); + tempFrameFormat.setDimensions(ow / 4, oh); + Frame temp = context.getFrameManager().newFrame(tempFrameFormat); + mProgram.process(input, temp); + + // Read frame from GPU to CPU. + Frame output = context.getFrameManager().newFrame(outputFormat); + output.setDataFromFrame(temp); + temp.release(); + + // Push output and yield ownership. + pushOutput("image", output); + output.release(); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBAFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBAFilter.java new file mode 100644 index 0000000..ab4814f --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBAFilter.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.util.Log; + +/** + * @hide + */ +public class ToRGBAFilter extends Filter { + + private int mInputBPP; + private Program mProgram; + private FrameFormat mLastFormat = null; + + public ToRGBAFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + MutableFrameFormat mask = new MutableFrameFormat(FrameFormat.TYPE_BYTE, + FrameFormat.TARGET_NATIVE); + mask.setDimensionCount(2); + addMaskedInputPort("image", mask); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return getConvertedFormat(inputFormat); + } + + public FrameFormat getConvertedFormat(FrameFormat format) { + MutableFrameFormat result = format.mutableCopy(); + result.setMetaValue(ImageFormat.COLORSPACE_KEY, ImageFormat.COLORSPACE_RGBA); + result.setBytesPerSample(4); + return result; + } + + public void createProgram(FilterContext context, FrameFormat format) { + mInputBPP = format.getBytesPerSample(); + if (mLastFormat != null && mLastFormat.getBytesPerSample() == mInputBPP) return; + mLastFormat = format; + switch (mInputBPP) { + case 1: + mProgram = new NativeProgram("filterpack_imageproc", "gray_to_rgba"); + break; + case 3: + mProgram = new NativeProgram("filterpack_imageproc", "rgb_to_rgba"); + break; + default: + throw new RuntimeException("Unsupported BytesPerPixel: " + mInputBPP + "!"); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + createProgram(context, input.getFormat()); + + // Create output frame + Frame output = context.getFrameManager().newFrame(getConvertedFormat(input.getFormat())); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBFilter.java new file mode 100644 index 0000000..9258502 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBFilter.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.util.Log; + +/** + * @hide + */ +public class ToRGBFilter extends Filter { + + private int mInputBPP; + private Program mProgram; + private FrameFormat mLastFormat = null; + + public ToRGBFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + MutableFrameFormat mask = new MutableFrameFormat(FrameFormat.TYPE_BYTE, + FrameFormat.TARGET_NATIVE); + mask.setDimensionCount(2); + addMaskedInputPort("image", mask); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return getConvertedFormat(inputFormat); + } + + public FrameFormat getConvertedFormat(FrameFormat format) { + MutableFrameFormat result = format.mutableCopy(); + result.setMetaValue(ImageFormat.COLORSPACE_KEY, ImageFormat.COLORSPACE_RGB); + result.setBytesPerSample(3); + return result; + } + + public void createProgram(FilterContext context, FrameFormat format) { + mInputBPP = format.getBytesPerSample(); + if (mLastFormat != null && mLastFormat.getBytesPerSample() == mInputBPP) return; + mLastFormat = format; + switch (mInputBPP) { + case 1: + mProgram = new NativeProgram("filterpack_imageproc", "gray_to_rgb"); + break; + case 4: + mProgram = new NativeProgram("filterpack_imageproc", "rgba_to_rgb"); + break; + default: + throw new RuntimeException("Unsupported BytesPerPixel: " + mInputBPP + "!"); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + createProgram(context, input.getFormat()); + + // Create output frame + Frame output = context.getFrameManager().newFrame(getConvertedFormat(input.getFormat())); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/VignetteFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/VignetteFilter.java new file mode 100644 index 0000000..2d78fff --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/VignetteFilter.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +public class VignetteFilter extends Filter { + + @GenerateFieldPort(name = "scale", hasDefault = true) + private float mScale = 0f; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final float mSlope = 20.0f; + private final float mShade = 0.85f; + + private final String mVignetteShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform float range;\n" + + "uniform float inv_max_dist;\n" + + "uniform float shade;\n" + + "uniform vec2 center;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " const float slope = 20.0;\n" + + " float dist = distance(gl_FragCoord.xy, center);\n" + + " float lumen = shade / (1.0 + exp((dist * inv_max_dist - range) * slope)) + (1.0 - shade);\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " gl_FragColor = vec4(color.rgb * lumen, color.a);\n" + + "}\n"; + + public VignetteFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mVignetteShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + private void initParameters() { + if (mProgram != null) { + float centerX = (float) (0.5 * mWidth); + float centerY = (float) (0.5 * mHeight); + float center[] = {centerX, centerY}; + float max_dist = (float) Math.sqrt(centerX * centerX + centerY * centerY); + + mProgram.setHostValue("center", center); + mProgram.setHostValue("inv_max_dist", 1.0f / max_dist); + mProgram.setHostValue("shade", mShade); + + updateParameters(); + } + } + + private void updateParameters() { + // The 'range' is between 1.3 to 0.6. When scale is zero then range is 1.3 + // which means no vignette at all because the luminousity difference is + // less than 1/256 and will cause nothing. + mProgram.setHostValue("range", 1.30f - (float) Math.sqrt(mScale) * 0.7f); + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateParameters(); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + mWidth = inputFormat.getWidth(); + mHeight = inputFormat.getHeight(); + initParameters(); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/package-info.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/package-info.java new file mode 100644 index 0000000..1cf48b0 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/package-info.java @@ -0,0 +1,4 @@ +/** + * @hide + */ +package android.filterpacks.imageproc; diff --git a/media/mca/filterpacks/java/android/filterpacks/numeric/SinWaveFilter.java b/media/mca/filterpacks/java/android/filterpacks/numeric/SinWaveFilter.java new file mode 100644 index 0000000..7e2b4cf --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/numeric/SinWaveFilter.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.numeric; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.format.ObjectFormat; + +import java.lang.Math; + +/** + * @hide + */ +public class SinWaveFilter extends Filter { + + @GenerateFieldPort(name = "stepSize", hasDefault = true) + private float mStepSize = 0.05f; + + private float mValue = 0.0f; + + private FrameFormat mOutputFormat; + + public SinWaveFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + mOutputFormat = ObjectFormat.fromClass(Float.class, FrameFormat.TARGET_SIMPLE); + addOutputPort("value", mOutputFormat); + } + + @Override + public void open(FilterContext env) { + mValue = 0.0f; + } + + @Override + public void process(FilterContext env) { + Frame output = env.getFrameManager().newFrame(mOutputFormat); + output.setObjectValue(((float)Math.sin(mValue) + 1.0f) / 2.0f); + pushOutput("value", output); + mValue += mStepSize; + output.release(); + } + + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/numeric/package-info.java b/media/mca/filterpacks/java/android/filterpacks/numeric/package-info.java new file mode 100644 index 0000000..55088eb --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/numeric/package-info.java @@ -0,0 +1,4 @@ +/** + * @hide + */ +package android.filterpacks.numeric; diff --git a/media/mca/filterpacks/java/android/filterpacks/performance/Throughput.java b/media/mca/filterpacks/java/android/filterpacks/performance/Throughput.java new file mode 100644 index 0000000..51f29f3 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/performance/Throughput.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.performance; + +/** + * @hide + */ +public class Throughput { + + private final int mTotalFrames; + private final int mPeriodFrames; + private final int mPeriodTime; + private final int mPixels; + + public Throughput(int totalFrames, int periodFrames, int periodTime, int pixels) { + mTotalFrames = totalFrames; + mPeriodFrames = periodFrames; + mPeriodTime = periodTime; + mPixels = pixels; + } + + public int getTotalFrameCount() { + return mTotalFrames; + } + + public int getPeriodFrameCount() { + return mPeriodFrames; + } + + public int getPeriodTime() { + return mPeriodTime; + } + + public float getFramesPerSecond() { + return mPeriodFrames / (float)mPeriodTime; + } + + public float getNanosPerPixel() { + double frameTimeInNanos = (mPeriodTime / (double)mPeriodFrames) * 1000000.0; + return (float)(frameTimeInNanos / mPixels); + } + + public String toString() { + return getFramesPerSecond() + " FPS"; + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/performance/ThroughputFilter.java b/media/mca/filterpacks/java/android/filterpacks/performance/ThroughputFilter.java new file mode 100644 index 0000000..ac837ed --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/performance/ThroughputFilter.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.performance; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.format.ObjectFormat; +import android.os.SystemClock; + +/** + * @hide + */ +public class ThroughputFilter extends Filter { + + @GenerateFieldPort(name = "period", hasDefault = true) + private int mPeriod = 5; + + private long mLastTime = 0; + + private int mTotalFrameCount = 0; + private int mPeriodFrameCount = 0; + + private FrameFormat mOutputFormat; + + public ThroughputFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + // Add input ports + addInputPort("frame"); + + // Add output ports + mOutputFormat = ObjectFormat.fromClass(Throughput.class, FrameFormat.TARGET_SIMPLE); + addOutputBasedOnInput("frame", "frame"); + addOutputPort("throughput", mOutputFormat); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + @Override + public void open(FilterContext env) { + mTotalFrameCount = 0; + mPeriodFrameCount = 0; + mLastTime = 0; + } + + @Override + public void process(FilterContext context) { + // Pass through input frame + Frame input = pullInput("frame"); + pushOutput("frame", input); + + // Update stats + ++mTotalFrameCount; + ++mPeriodFrameCount; + + // Check clock + if (mLastTime == 0) { + mLastTime = SystemClock.elapsedRealtime(); + } + long curTime = SystemClock.elapsedRealtime(); + + // Output throughput info if time period is up + if ((curTime - mLastTime) >= (mPeriod * 1000)) { + FrameFormat inputFormat = input.getFormat(); + int pixelCount = inputFormat.getWidth() * inputFormat.getHeight(); + Throughput throughput = new Throughput(mTotalFrameCount, + mPeriodFrameCount, + mPeriod, + pixelCount); + Frame throughputFrame = context.getFrameManager().newFrame(mOutputFormat); + throughputFrame.setObjectValue(throughput); + pushOutput("throughput", throughputFrame); + mLastTime = curTime; + mPeriodFrameCount = 0; + } + } + + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/performance/package-info.java b/media/mca/filterpacks/java/android/filterpacks/performance/package-info.java new file mode 100644 index 0000000..8b77bbb --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/performance/package-info.java @@ -0,0 +1,4 @@ +/** + * @hide + */ +package android.filterpacks.performance; diff --git a/media/mca/filterpacks/java/android/filterpacks/text/StringLogger.java b/media/mca/filterpacks/java/android/filterpacks/text/StringLogger.java new file mode 100644 index 0000000..8c7cd69 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/text/StringLogger.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.text; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.format.ObjectFormat; +import android.util.Log; + +/** + * @hide + */ +public class StringLogger extends Filter { + + public StringLogger(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("string", ObjectFormat.fromClass(Object.class, + FrameFormat.TARGET_SIMPLE)); + } + + @Override + public void process(FilterContext env) { + Frame input = pullInput("string"); + String inputString = input.getObjectValue().toString(); + Log.i("StringLogger", inputString); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/text/StringSource.java b/media/mca/filterpacks/java/android/filterpacks/text/StringSource.java new file mode 100644 index 0000000..cc33b89 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/text/StringSource.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.text; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.format.ObjectFormat; + +/** + * @hide + */ +public class StringSource extends Filter { + + @GenerateFieldPort(name = "stringValue") + private String mString; + + private FrameFormat mOutputFormat; + + public StringSource(String name) { + super(name); + } + + @Override + public void setupPorts() { + mOutputFormat = ObjectFormat.fromClass(String.class, FrameFormat.TARGET_SIMPLE); + addOutputPort("string", mOutputFormat); + } + + @Override + public void process(FilterContext env) { + Frame output = env.getFrameManager().newFrame(mOutputFormat); + output.setObjectValue(mString); + output.setTimestamp(Frame.TIMESTAMP_UNKNOWN); + pushOutput("string", output); + closeOutputPort("string"); + } + + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/text/ToUpperCase.java b/media/mca/filterpacks/java/android/filterpacks/text/ToUpperCase.java new file mode 100644 index 0000000..0cf3477 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/text/ToUpperCase.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.text; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.format.ObjectFormat; + +/** + * @hide + */ +public class ToUpperCase extends Filter { + + private FrameFormat mOutputFormat; + + public ToUpperCase(String name) { + super(name); + } + + @Override + public void setupPorts() { + mOutputFormat = ObjectFormat.fromClass(String.class, FrameFormat.TARGET_SIMPLE); + addMaskedInputPort("mixedcase", mOutputFormat); + addOutputPort("uppercase", mOutputFormat); + } + + @Override + public void process(FilterContext env) { + Frame input = pullInput("mixedcase"); + String inputString = (String)input.getObjectValue(); + + Frame output = env.getFrameManager().newFrame(mOutputFormat); + output.setObjectValue(inputString.toUpperCase()); + + pushOutput("uppercase", output); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/text/package-info.java b/media/mca/filterpacks/java/android/filterpacks/text/package-info.java new file mode 100644 index 0000000..371d3c1 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/text/package-info.java @@ -0,0 +1,4 @@ +/** + * @hide + */ +package android.filterpacks.text; diff --git a/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceRenderFilter.java b/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceRenderFilter.java new file mode 100644 index 0000000..a5c1ccb --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceRenderFilter.java @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.ui; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.FilterSurfaceView; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.GLEnvironment; +import android.filterfw.core.GLFrame; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import android.graphics.Rect; + +import android.util.Log; + +/** + * @hide + */ +public class SurfaceRenderFilter extends Filter implements SurfaceHolder.Callback { + + private final int RENDERMODE_STRETCH = 0; + private final int RENDERMODE_FIT = 1; + private final int RENDERMODE_FILL_CROP = 2; + + /** Required. Sets the destination filter surface view for this + * node. + */ + @GenerateFinalPort(name = "surfaceView") + private FilterSurfaceView mSurfaceView; + + /** Optional. Control how the incoming frames are rendered onto the + * output. Default is FIT. + * RENDERMODE_STRETCH: Just fill the output surfaceView. + * RENDERMODE_FIT: Keep aspect ratio and fit without cropping. May + * have black bars. + * RENDERMODE_FILL_CROP: Keep aspect ratio and fit without black + * bars. May crop. + */ + @GenerateFieldPort(name = "renderMode", hasDefault = true) + private String mRenderModeString; + + private boolean mIsBound = false; + + private ShaderProgram mProgram; + private GLFrame mScreen; + private int mRenderMode = RENDERMODE_FIT; + private float mAspectRatio = 1.f; + + private int mScreenWidth; + private int mScreenHeight; + + private boolean mLogVerbose; + private static final String TAG = "SurfaceRenderFilter"; + + public SurfaceRenderFilter(String name) { + super(name); + + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + @Override + public void setupPorts() { + // Make sure we have a SurfaceView + if (mSurfaceView == null) { + throw new RuntimeException("NULL SurfaceView passed to SurfaceRenderFilter"); + } + + // Add input port + addMaskedInputPort("frame", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + } + + public void updateRenderMode() { + if (mRenderModeString != null) { + if (mRenderModeString.equals("stretch")) { + mRenderMode = RENDERMODE_STRETCH; + } else if (mRenderModeString.equals("fit")) { + mRenderMode = RENDERMODE_FIT; + } else if (mRenderModeString.equals("fill_crop")) { + mRenderMode = RENDERMODE_FILL_CROP; + } else { + throw new RuntimeException("Unknown render mode '" + mRenderModeString + "'!"); + } + } + updateTargetRect(); + } + + @Override + public void prepare(FilterContext context) { + // Create identity shader to render, and make sure to render upside-down, as textures + // are stored internally bottom-to-top. + mProgram = ShaderProgram.createIdentity(context); + mProgram.setSourceRect(0, 1, 1, -1); + mProgram.setClearsOutput(true); + mProgram.setClearColor(0.0f, 0.0f, 0.0f); + + updateRenderMode(); + + // Create a frame representing the screen + MutableFrameFormat screenFormat = ImageFormat.create(mSurfaceView.getWidth(), + mSurfaceView.getHeight(), + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + mScreen = (GLFrame)context.getFrameManager().newBoundFrame(screenFormat, + GLFrame.EXISTING_FBO_BINDING, + 0); + } + + @Override + public void open(FilterContext context) { + // Bind surface view to us. This will emit a surfaceCreated and surfaceChanged call that + // will update our screen width and height. + mSurfaceView.unbind(); + mSurfaceView.bindToListener(this, context.getGLEnvironment()); + } + + @Override + public void process(FilterContext context) { + // Make sure we are bound to a surface before rendering + if (!mIsBound) { + Log.w("SurfaceRenderFilter", + this + ": Ignoring frame as there is no surface to render to!"); + return; + } + + if (mLogVerbose) Log.v(TAG, "Starting frame processing"); + + GLEnvironment glEnv = mSurfaceView.getGLEnv(); + if (glEnv != context.getGLEnvironment()) { + throw new RuntimeException("Surface created under different GLEnvironment!"); + } + + + // Get input frame + Frame input = pullInput("frame"); + boolean createdFrame = false; + + float currentAspectRatio = (float)input.getFormat().getWidth() / input.getFormat().getHeight(); + if (currentAspectRatio != mAspectRatio) { + if (mLogVerbose) Log.v(TAG, "New aspect ratio: " + currentAspectRatio +", previously: " + mAspectRatio); + mAspectRatio = currentAspectRatio; + updateTargetRect(); + } + + // See if we need to copy to GPU + Frame gpuFrame = null; + if (mLogVerbose) Log.v("SurfaceRenderFilter", "Got input format: " + input.getFormat()); + int target = input.getFormat().getTarget(); + if (target != FrameFormat.TARGET_GPU) { + gpuFrame = context.getFrameManager().duplicateFrameToTarget(input, + FrameFormat.TARGET_GPU); + createdFrame = true; + } else { + gpuFrame = input; + } + + // Activate our surface + glEnv.activateSurfaceWithId(mSurfaceView.getSurfaceId()); + + // Process + mProgram.process(gpuFrame, mScreen); + + // And swap buffers + glEnv.swapBuffers(); + + if (createdFrame) { + gpuFrame.release(); + } + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + updateTargetRect(); + } + + @Override + public void close(FilterContext context) { + mSurfaceView.unbind(); + } + + @Override + public void tearDown(FilterContext context) { + if (mScreen != null) { + mScreen.release(); + } + } + + @Override + public synchronized void surfaceCreated(SurfaceHolder holder) { + mIsBound = true; + } + + @Override + public synchronized void surfaceChanged(SurfaceHolder holder, + int format, + int width, + int height) { + // If the screen is null, we do not care about surface changes (yet). Once we have a + // screen object, we need to keep track of these changes. + if (mScreen != null) { + mScreenWidth = width; + mScreenHeight = height; + mScreen.setViewport(0, 0, mScreenWidth, mScreenHeight); + updateTargetRect(); + } + } + + @Override + public synchronized void surfaceDestroyed(SurfaceHolder holder) { + mIsBound = false; + } + + private void updateTargetRect() { + if (mScreenWidth > 0 && mScreenHeight > 0 && mProgram != null) { + float screenAspectRatio = (float)mScreenWidth / mScreenHeight; + float relativeAspectRatio = screenAspectRatio / mAspectRatio; + + switch (mRenderMode) { + case RENDERMODE_STRETCH: + mProgram.setTargetRect(0, 0, 1, 1); + break; + case RENDERMODE_FIT: + if (relativeAspectRatio > 1.0f) { + // Screen is wider than the camera, scale down X + mProgram.setTargetRect(0.5f - 0.5f / relativeAspectRatio, 0.0f, + 1.0f / relativeAspectRatio, 1.0f); + } else { + // Screen is taller than the camera, scale down Y + mProgram.setTargetRect(0.0f, 0.5f - 0.5f * relativeAspectRatio, + 1.0f, relativeAspectRatio); + } + break; + case RENDERMODE_FILL_CROP: + if (relativeAspectRatio > 1) { + // Screen is wider than the camera, crop in Y + mProgram.setTargetRect(0.0f, 0.5f - 0.5f * relativeAspectRatio, + 1.0f, relativeAspectRatio); + } else { + // Screen is taller than the camera, crop in X + mProgram.setTargetRect(0.5f - 0.5f / relativeAspectRatio, 0.0f, + 1.0f / relativeAspectRatio, 1.0f); + } + break; + } + } + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceTargetFilter.java b/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceTargetFilter.java new file mode 100644 index 0000000..308d168 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceTargetFilter.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.ui; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.GLEnvironment; +import android.filterfw.core.GLFrame; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import android.graphics.Rect; + +import android.util.Log; + +/** + * @hide + */ +public class SurfaceTargetFilter extends Filter { + + private final int RENDERMODE_STRETCH = 0; + private final int RENDERMODE_FIT = 1; + private final int RENDERMODE_FILL_CROP = 2; + + /** Required. Sets the destination surface for this node. This assumes that + * higher-level code is ensuring that the surface is valid, and properly + * updates Surface parameters if they change. + */ + @GenerateFinalPort(name = "surface") + private Surface mSurface; + + /** Required. Width of the output surface */ + @GenerateFieldPort(name = "owidth") + private int mScreenWidth; + + /** Required. Height of the output surface */ + @GenerateFieldPort(name = "oheight") + private int mScreenHeight; + + /** Optional. Control how the incoming frames are rendered onto the + * output. Default is FIT. + * RENDERMODE_STRETCH: Just fill the output surfaceView. + * RENDERMODE_FIT: Keep aspect ratio and fit without cropping. May + * have black bars. + * RENDERMODE_FILL_CROP: Keep aspect ratio and fit without black + * bars. May crop. + */ + @GenerateFieldPort(name = "renderMode", hasDefault = true) + private String mRenderModeString; + + private ShaderProgram mProgram; + private GLEnvironment mGlEnv; + private GLFrame mScreen; + private int mRenderMode = RENDERMODE_FIT; + private float mAspectRatio = 1.f; + + private int mSurfaceId = -1; + + private boolean mLogVerbose; + private static final String TAG = "SurfaceRenderFilter"; + + public SurfaceTargetFilter(String name) { + super(name); + + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + @Override + public void setupPorts() { + // Make sure we have a Surface + if (mSurface == null) { + throw new RuntimeException("NULL Surface passed to SurfaceTargetFilter"); + } + + // Add input port + addMaskedInputPort("frame", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + } + + public void updateRenderMode() { + if (mRenderModeString != null) { + if (mRenderModeString.equals("stretch")) { + mRenderMode = RENDERMODE_STRETCH; + } else if (mRenderModeString.equals("fit")) { + mRenderMode = RENDERMODE_FIT; + } else if (mRenderModeString.equals("fill_crop")) { + mRenderMode = RENDERMODE_FILL_CROP; + } else { + throw new RuntimeException("Unknown render mode '" + mRenderModeString + "'!"); + } + } + updateTargetRect(); + } + + @Override + public void prepare(FilterContext context) { + mGlEnv = context.getGLEnvironment(); + + // Create identity shader to render, and make sure to render upside-down, as textures + // are stored internally bottom-to-top. + mProgram = ShaderProgram.createIdentity(context); + mProgram.setSourceRect(0, 1, 1, -1); + mProgram.setClearsOutput(true); + mProgram.setClearColor(0.0f, 0.0f, 0.0f); + + MutableFrameFormat screenFormat = ImageFormat.create(mScreenWidth, + mScreenHeight, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + mScreen = (GLFrame)context.getFrameManager().newBoundFrame(screenFormat, + GLFrame.EXISTING_FBO_BINDING, + 0); + + // Set up cropping + updateRenderMode(); + } + + @Override + public void open(FilterContext context) { + registerSurface(); + } + + @Override + public void process(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Starting frame processing"); + + // Get input frame + Frame input = pullInput("frame"); + boolean createdFrame = false; + + float currentAspectRatio = (float)input.getFormat().getWidth() / input.getFormat().getHeight(); + if (currentAspectRatio != mAspectRatio) { + if (mLogVerbose) Log.v(TAG, "New aspect ratio: " + currentAspectRatio +", previously: " + mAspectRatio); + mAspectRatio = currentAspectRatio; + updateTargetRect(); + } + + // See if we need to copy to GPU + Frame gpuFrame = null; + if (mLogVerbose) Log.v("SurfaceRenderFilter", "Got input format: " + input.getFormat()); + int target = input.getFormat().getTarget(); + if (target != FrameFormat.TARGET_GPU) { + gpuFrame = context.getFrameManager().duplicateFrameToTarget(input, + FrameFormat.TARGET_GPU); + createdFrame = true; + } else { + gpuFrame = input; + } + + // Activate our surface + mGlEnv.activateSurfaceWithId(mSurfaceId); + + // Process + mProgram.process(gpuFrame, mScreen); + + // And swap buffers + mGlEnv.swapBuffers(); + + if (createdFrame) { + gpuFrame.release(); + } + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + mScreen.setViewport(0, 0, mScreenWidth, mScreenHeight); + updateTargetRect(); + } + + @Override + public void close(FilterContext context) { + unregisterSurface(); + } + + @Override + public void tearDown(FilterContext context) { + if (mScreen != null) { + mScreen.release(); + } + } + + private void updateTargetRect() { + if (mScreenWidth > 0 && mScreenHeight > 0 && mProgram != null) { + float screenAspectRatio = (float)mScreenWidth / mScreenHeight; + float relativeAspectRatio = screenAspectRatio / mAspectRatio; + + switch (mRenderMode) { + case RENDERMODE_STRETCH: + mProgram.setTargetRect(0, 0, 1, 1); + break; + case RENDERMODE_FIT: + if (relativeAspectRatio > 1.0f) { + // Screen is wider than the camera, scale down X + mProgram.setTargetRect(0.5f - 0.5f / relativeAspectRatio, 0.0f, + 1.0f / relativeAspectRatio, 1.0f); + } else { + // Screen is taller than the camera, scale down Y + mProgram.setTargetRect(0.0f, 0.5f - 0.5f * relativeAspectRatio, + 1.0f, relativeAspectRatio); + } + break; + case RENDERMODE_FILL_CROP: + if (relativeAspectRatio > 1) { + // Screen is wider than the camera, crop in Y + mProgram.setTargetRect(0.0f, 0.5f - 0.5f * relativeAspectRatio, + 1.0f, relativeAspectRatio); + } else { + // Screen is taller than the camera, crop in X + mProgram.setTargetRect(0.5f - 0.5f / relativeAspectRatio, 0.0f, + 1.0f / relativeAspectRatio, 1.0f); + } + break; + } + } + } + + private void registerSurface() { + mSurfaceId = mGlEnv.registerSurface(mSurface); + if (mSurfaceId < 0) { + throw new RuntimeException("Could not register Surface: " + mSurface); + } + } + + private void unregisterSurface() { + if (mSurfaceId > 0) { + mGlEnv.unregisterSurfaceId(mSurfaceId); + } + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/ui/package-info.java b/media/mca/filterpacks/java/android/filterpacks/ui/package-info.java new file mode 100644 index 0000000..9ed7d51 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/ui/package-info.java @@ -0,0 +1,4 @@ +/** + * @hide + */ +package android.filterpacks.ui; diff --git a/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java b/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java new file mode 100644 index 0000000..52c9fda --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java @@ -0,0 +1,976 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.videoproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.Frame; +import android.filterfw.core.GLFrame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.opengl.GLES20; +import android.os.SystemClock; +import android.util.Log; + +import java.lang.ArrayIndexOutOfBoundsException; +import java.lang.Math; +import java.util.Arrays; +import java.nio.ByteBuffer; + +/** + * @hide + */ +public class BackDropperFilter extends Filter { + /** User-visible parameters */ + + private final int BACKGROUND_STRETCH = 0; + private final int BACKGROUND_FIT = 1; + private final int BACKGROUND_FILL_CROP = 2; + + @GenerateFieldPort(name = "backgroundFitMode", hasDefault = true) + private int mBackgroundFitMode = BACKGROUND_FILL_CROP; + @GenerateFieldPort(name = "learningDuration", hasDefault = true) + private int mLearningDuration = DEFAULT_LEARNING_DURATION; + @GenerateFieldPort(name = "learningVerifyDuration", hasDefault = true) + private int mLearningVerifyDuration = DEFAULT_LEARNING_VERIFY_DURATION; + @GenerateFieldPort(name = "acceptStddev", hasDefault = true) + private float mAcceptStddev = DEFAULT_ACCEPT_STDDEV; + @GenerateFieldPort(name = "hierLrgScale", hasDefault = true) + private float mHierarchyLrgScale = DEFAULT_HIER_LRG_SCALE; + @GenerateFieldPort(name = "hierMidScale", hasDefault = true) + private float mHierarchyMidScale = DEFAULT_HIER_MID_SCALE; + @GenerateFieldPort(name = "hierSmlScale", hasDefault = true) + private float mHierarchySmlScale = DEFAULT_HIER_SML_SCALE; + + // Dimensions of foreground / background mask. Optimum value should take into account only + // image contents, NOT dimensions of input video stream. + @GenerateFieldPort(name = "maskWidthExp", hasDefault = true) + private int mMaskWidthExp = DEFAULT_MASK_WIDTH_EXPONENT; + @GenerateFieldPort(name = "maskHeightExp", hasDefault = true) + private int mMaskHeightExp = DEFAULT_MASK_HEIGHT_EXPONENT; + + // Levels at which to compute foreground / background decision. Think of them as are deltas + // SUBTRACTED from maskWidthExp and maskHeightExp. + @GenerateFieldPort(name = "hierLrgExp", hasDefault = true) + private int mHierarchyLrgExp = DEFAULT_HIER_LRG_EXPONENT; + @GenerateFieldPort(name = "hierMidExp", hasDefault = true) + private int mHierarchyMidExp = DEFAULT_HIER_MID_EXPONENT; + @GenerateFieldPort(name = "hierSmlExp", hasDefault = true) + private int mHierarchySmlExp = DEFAULT_HIER_SML_EXPONENT; + + @GenerateFieldPort(name = "lumScale", hasDefault = true) + private float mLumScale = DEFAULT_Y_SCALE_FACTOR; + @GenerateFieldPort(name = "chromaScale", hasDefault = true) + private float mChromaScale = DEFAULT_UV_SCALE_FACTOR; + @GenerateFieldPort(name = "maskBg", hasDefault = true) + private float mMaskBg = DEFAULT_MASK_BLEND_BG; + @GenerateFieldPort(name = "maskFg", hasDefault = true) + private float mMaskFg = DEFAULT_MASK_BLEND_FG; + @GenerateFieldPort(name = "exposureChange", hasDefault = true) + private float mExposureChange = DEFAULT_EXPOSURE_CHANGE; + @GenerateFieldPort(name = "whitebalanceredChange", hasDefault = true) + private float mWhiteBalanceRedChange = DEFAULT_WHITE_BALANCE_RED_CHANGE; + @GenerateFieldPort(name = "whitebalanceblueChange", hasDefault = true) + private float mWhiteBalanceBlueChange = DEFAULT_WHITE_BALANCE_BLUE_CHANGE; + @GenerateFieldPort(name = "autowbToggle", hasDefault = true) + private int mAutoWBToggle = DEFAULT_WHITE_BALANCE_TOGGLE; + + // TODO: These are not updatable: + @GenerateFieldPort(name = "learningAdaptRate", hasDefault = true) + private float mAdaptRateLearning = DEFAULT_LEARNING_ADAPT_RATE; + @GenerateFieldPort(name = "adaptRateBg", hasDefault = true) + private float mAdaptRateBg = DEFAULT_ADAPT_RATE_BG; + @GenerateFieldPort(name = "adaptRateFg", hasDefault = true) + private float mAdaptRateFg = DEFAULT_ADAPT_RATE_FG; + @GenerateFieldPort(name = "maskVerifyRate", hasDefault = true) + private float mVerifyRate = DEFAULT_MASK_VERIFY_RATE; + @GenerateFieldPort(name = "learningDoneListener", hasDefault = true) + private LearningDoneListener mLearningDoneListener = null; + + @GenerateFieldPort(name = "useTheForce", hasDefault = true) + private boolean mUseTheForce = false; + + @GenerateFinalPort(name = "provideDebugOutputs", hasDefault = true) + private boolean mProvideDebugOutputs = false; + + // Whether to mirror the background or not. For ex, the Camera app + // would mirror the preview for the front camera + @GenerateFieldPort(name = "mirrorBg", hasDefault = true) + private boolean mMirrorBg = false; + + // The orientation of the display. This will change the flipping + // coordinates, if we were to mirror the background + @GenerateFieldPort(name = "orientation", hasDefault = true) + private int mOrientation = 0; + + /** Default algorithm parameter values, for non-shader use */ + + // Frame count for learning bg model + private static final int DEFAULT_LEARNING_DURATION = 40; + // Frame count for learning verification + private static final int DEFAULT_LEARNING_VERIFY_DURATION = 10; + // Maximum distance (in standard deviations) for considering a pixel as background + private static final float DEFAULT_ACCEPT_STDDEV = 0.85f; + // Variance threshold scale factor for large scale of hierarchy + private static final float DEFAULT_HIER_LRG_SCALE = 0.7f; + // Variance threshold scale factor for medium scale of hierarchy + private static final float DEFAULT_HIER_MID_SCALE = 0.6f; + // Variance threshold scale factor for small scale of hierarchy + private static final float DEFAULT_HIER_SML_SCALE = 0.5f; + // Width of foreground / background mask. + private static final int DEFAULT_MASK_WIDTH_EXPONENT = 8; + // Height of foreground / background mask. + private static final int DEFAULT_MASK_HEIGHT_EXPONENT = 8; + // Area over which to average for large scale (length in pixels = 2^HIERARCHY_*_EXPONENT) + private static final int DEFAULT_HIER_LRG_EXPONENT = 3; + // Area over which to average for medium scale + private static final int DEFAULT_HIER_MID_EXPONENT = 2; + // Area over which to average for small scale + private static final int DEFAULT_HIER_SML_EXPONENT = 0; + // Scale factor for luminance channel in distance calculations (larger = more significant) + private static final float DEFAULT_Y_SCALE_FACTOR = 0.40f; + // Scale factor for chroma channels in distance calculations + private static final float DEFAULT_UV_SCALE_FACTOR = 1.35f; + // Mask value to start blending away from background + private static final float DEFAULT_MASK_BLEND_BG = 0.65f; + // Mask value to start blending away from foreground + private static final float DEFAULT_MASK_BLEND_FG = 0.95f; + // Exposure stop number to change the brightness of foreground + private static final float DEFAULT_EXPOSURE_CHANGE = 1.0f; + // White balance change in Red channel for foreground + private static final float DEFAULT_WHITE_BALANCE_RED_CHANGE = 0.0f; + // White balance change in Blue channel for foreground + private static final float DEFAULT_WHITE_BALANCE_BLUE_CHANGE = 0.0f; + // Variable to control automatic white balance effect + // 0.f -> Auto WB is off; 1.f-> Auto WB is on + private static final int DEFAULT_WHITE_BALANCE_TOGGLE = 0; + + // Default rate at which to learn bg model during learning period + private static final float DEFAULT_LEARNING_ADAPT_RATE = 0.2f; + // Default rate at which to learn bg model from new background pixels + private static final float DEFAULT_ADAPT_RATE_BG = 0.0f; + // Default rate at which to learn bg model from new foreground pixels + private static final float DEFAULT_ADAPT_RATE_FG = 0.0f; + // Default rate at which to verify whether background is stable + private static final float DEFAULT_MASK_VERIFY_RATE = 0.25f; + // Default rate at which to verify whether background is stable + private static final int DEFAULT_LEARNING_DONE_THRESHOLD = 20; + + // Default 3x3 matrix, column major, for fitting background 1:1 + private static final float[] DEFAULT_BG_FIT_TRANSFORM = new float[] { + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f + }; + + /** Default algorithm parameter values, for shader use */ + + // Area over which to blur binary mask values (length in pixels = 2^MASK_SMOOTH_EXPONENT) + private static final String MASK_SMOOTH_EXPONENT = "2.0"; + // Scale value for mapping variance distance to fit nicely to 0-1, 8-bit + private static final String DISTANCE_STORAGE_SCALE = "0.6"; + // Scale value for mapping variance to fit nicely to 0-1, 8-bit + private static final String VARIANCE_STORAGE_SCALE = "5.0"; + // Default scale of auto white balance parameters + private static final String DEFAULT_AUTO_WB_SCALE = "0.25"; + // Minimum variance (0-255 scale) + private static final String MIN_VARIANCE = "3.0"; + // Column-major array for 4x4 matrix converting RGB to YCbCr, JPEG definition (no pedestal) + private static final String RGB_TO_YUV_MATRIX = "0.299, -0.168736, 0.5, 0.000, " + + "0.587, -0.331264, -0.418688, 0.000, " + + "0.114, 0.5, -0.081312, 0.000, " + + "0.000, 0.5, 0.5, 1.000 "; + /** Stream names */ + + private static final String[] mInputNames = {"video", + "background"}; + + private static final String[] mOutputNames = {"video"}; + + private static final String[] mDebugOutputNames = {"debug1", + "debug2"}; + + /** Other private variables */ + + private FrameFormat mOutputFormat; + private MutableFrameFormat mMemoryFormat; + private MutableFrameFormat mMaskFormat; + private MutableFrameFormat mAverageFormat; + + private final boolean mLogVerbose; + private static final String TAG = "BackDropperFilter"; + + /** Shader source code */ + + // Shared uniforms and utility functions + private static String mSharedUtilShader = + "precision mediump float;\n" + + "uniform float fg_adapt_rate;\n" + + "uniform float bg_adapt_rate;\n" + + "const mat4 coeff_yuv = mat4(" + RGB_TO_YUV_MATRIX + ");\n" + + "const float dist_scale = " + DISTANCE_STORAGE_SCALE + ";\n" + + "const float inv_dist_scale = 1. / dist_scale;\n" + + "const float var_scale=" + VARIANCE_STORAGE_SCALE + ";\n" + + "const float inv_var_scale = 1. / var_scale;\n" + + "const float min_variance = inv_var_scale *" + MIN_VARIANCE + "/ 256.;\n" + + "const float auto_wb_scale = " + DEFAULT_AUTO_WB_SCALE + ";\n" + + "\n" + + // Variance distance in luminance between current pixel and background model + "float gauss_dist_y(float y, float mean, float variance) {\n" + + " float dist = (y - mean) * (y - mean) / variance;\n" + + " return dist;\n" + + "}\n" + + // Sum of variance distances in chroma between current pixel and background + // model + "float gauss_dist_uv(vec2 uv, vec2 mean, vec2 variance) {\n" + + " vec2 dist = (uv - mean) * (uv - mean) / variance;\n" + + " return dist.r + dist.g;\n" + + "}\n" + + // Select learning rate for pixel based on smoothed decision mask alpha + "float local_adapt_rate(float alpha) {\n" + + " return mix(bg_adapt_rate, fg_adapt_rate, alpha);\n" + + "}\n" + + "\n"; + + // Distance calculation shader. Calculates a distance metric between the foreground and the + // current background model, in both luminance and in chroma (yuv space). Distance is + // measured in variances from the mean background value. For chroma, the distance is the sum + // of the two individual color channel distances. The distances are output on the b and alpha + // channels, r and g are for debug information. + // Inputs: + // tex_sampler_0: Mip-map for foreground (live) video frame. + // tex_sampler_1: Background mean mask. + // tex_sampler_2: Background variance mask. + // subsample_level: Level on foreground frame's mip-map. + private static final String mBgDistanceShader = + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform sampler2D tex_sampler_2;\n" + + "uniform float subsample_level;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 fg_rgb = texture2D(tex_sampler_0, v_texcoord, subsample_level);\n" + + " vec4 fg = coeff_yuv * vec4(fg_rgb.rgb, 1.);\n" + + " vec4 mean = texture2D(tex_sampler_1, v_texcoord);\n" + + " vec4 variance = inv_var_scale * texture2D(tex_sampler_2, v_texcoord);\n" + + "\n" + + " float dist_y = gauss_dist_y(fg.r, mean.r, variance.r);\n" + + " float dist_uv = gauss_dist_uv(fg.gb, mean.gb, variance.gb);\n" + + " gl_FragColor = vec4(0.5*fg.rg, dist_scale*dist_y, dist_scale*dist_uv);\n" + + "}\n"; + + // Foreground/background mask decision shader. Decides whether a frame is in the foreground or + // the background using a hierarchical threshold on the distance. Binary foreground/background + // mask is placed in the alpha channel. The RGB channels contain debug information. + private static final String mBgMaskShader = + "uniform sampler2D tex_sampler_0;\n" + + "uniform float accept_variance;\n" + + "uniform vec2 yuv_weights;\n" + + "uniform float scale_lrg;\n" + + "uniform float scale_mid;\n" + + "uniform float scale_sml;\n" + + "uniform float exp_lrg;\n" + + "uniform float exp_mid;\n" + + "uniform float exp_sml;\n" + + "varying vec2 v_texcoord;\n" + + // Decide whether pixel is foreground or background based on Y and UV + // distance and maximum acceptable variance. + // yuv_weights.x is smaller than yuv_weights.y to discount the influence of shadow + "bool is_fg(vec2 dist_yc, float accept_variance) {\n" + + " return ( dot(yuv_weights, dist_yc) >= accept_variance );\n" + + "}\n" + + "void main() {\n" + + " vec4 dist_lrg_sc = texture2D(tex_sampler_0, v_texcoord, exp_lrg);\n" + + " vec4 dist_mid_sc = texture2D(tex_sampler_0, v_texcoord, exp_mid);\n" + + " vec4 dist_sml_sc = texture2D(tex_sampler_0, v_texcoord, exp_sml);\n" + + " vec2 dist_lrg = inv_dist_scale * dist_lrg_sc.ba;\n" + + " vec2 dist_mid = inv_dist_scale * dist_mid_sc.ba;\n" + + " vec2 dist_sml = inv_dist_scale * dist_sml_sc.ba;\n" + + " vec2 norm_dist = 0.75 * dist_sml / accept_variance;\n" + // For debug viz + " bool is_fg_lrg = is_fg(dist_lrg, accept_variance * scale_lrg);\n" + + " bool is_fg_mid = is_fg_lrg || is_fg(dist_mid, accept_variance * scale_mid);\n" + + " float is_fg_sml =\n" + + " float(is_fg_mid || is_fg(dist_sml, accept_variance * scale_sml));\n" + + " float alpha = 0.5 * is_fg_sml + 0.3 * float(is_fg_mid) + 0.2 * float(is_fg_lrg);\n" + + " gl_FragColor = vec4(alpha, norm_dist, is_fg_sml);\n" + + "}\n"; + + // Automatic White Balance parameter decision shader + // Use the Gray World assumption that in a white balance corrected image, the average of R, G, B + // channel will be a common gray value. + // To match the white balance of foreground and background, the average of R, G, B channel of + // two videos should match. + // Inputs: + // tex_sampler_0: Mip-map for foreground (live) video frame. + // tex_sampler_1: Mip-map for background (playback) video frame. + // pyramid_depth: Depth of input frames' mip-maps. + private static final String mAutomaticWhiteBalance = + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform float pyramid_depth;\n" + + "uniform bool autowb_toggle;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 mean_video = texture2D(tex_sampler_0, v_texcoord, pyramid_depth);\n"+ + " vec4 mean_bg = texture2D(tex_sampler_1, v_texcoord, pyramid_depth);\n" + + // If Auto WB is toggled off, the return texture will be a unicolor texture of value 1 + // If Auto WB is toggled on, the return texture will be a unicolor texture with + // adjustment parameters for R and B channels stored in the corresponding channel + " float green_normalizer = mean_video.g / mean_bg.g;\n"+ + " vec4 adjusted_value = vec4(mean_bg.r / mean_video.r * green_normalizer, 1., \n" + + " mean_bg.b / mean_video.b * green_normalizer, 1.) * auto_wb_scale; \n" + + " gl_FragColor = autowb_toggle ? adjusted_value : vec4(auto_wb_scale);\n" + + "}\n"; + + + // Background subtraction shader. Uses a mipmap of the binary mask map to blend smoothly between + // foreground and background + // Inputs: + // tex_sampler_0: Foreground (live) video frame. + // tex_sampler_1: Background (playback) video frame. + // tex_sampler_2: Foreground/background mask. + // tex_sampler_3: Auto white-balance factors. + private static final String mBgSubtractShader = + "uniform mat3 bg_fit_transform;\n" + + "uniform float mask_blend_bg;\n" + + "uniform float mask_blend_fg;\n" + + "uniform float exposure_change;\n" + + "uniform float whitebalancered_change;\n" + + "uniform float whitebalanceblue_change;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform sampler2D tex_sampler_2;\n" + + "uniform sampler2D tex_sampler_3;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec2 bg_texcoord = (bg_fit_transform * vec3(v_texcoord, 1.)).xy;\n" + + " vec4 bg_rgb = texture2D(tex_sampler_1, bg_texcoord);\n" + + // The foreground texture is modified by multiplying both manual and auto white balance changes in R and B + // channel and multiplying exposure change in all R, G, B channels. + " vec4 wb_auto_scale = texture2D(tex_sampler_3, v_texcoord) * exposure_change / auto_wb_scale;\n" + + " vec4 wb_manual_scale = vec4(1. + whitebalancered_change, 1., 1. + whitebalanceblue_change, 1.);\n" + + " vec4 fg_rgb = texture2D(tex_sampler_0, v_texcoord);\n" + + " vec4 fg_adjusted = fg_rgb * wb_manual_scale * wb_auto_scale;\n"+ + " vec4 mask = texture2D(tex_sampler_2, v_texcoord, \n" + + " " + MASK_SMOOTH_EXPONENT + ");\n" + + " float alpha = smoothstep(mask_blend_bg, mask_blend_fg, mask.a);\n" + + " gl_FragColor = mix(bg_rgb, fg_adjusted, alpha);\n"; + + // May the Force... Makes the foreground object translucent blue, with a bright + // blue-white outline + private static final String mBgSubtractForceShader = + " vec4 ghost_rgb = (fg_adjusted * 0.7 + vec4(0.3,0.3,0.4,0.))*0.65 + \n" + + " 0.35*bg_rgb;\n" + + " float glow_start = 0.75 * mask_blend_bg; \n"+ + " float glow_max = mask_blend_bg; \n"+ + " gl_FragColor = mask.a < glow_start ? bg_rgb : \n" + + " mask.a < glow_max ? mix(bg_rgb, vec4(0.9,0.9,1.0,1.0), \n" + + " (mask.a - glow_start) / (glow_max - glow_start) ) : \n" + + " mask.a < mask_blend_fg ? mix(vec4(0.9,0.9,1.0,1.0), ghost_rgb, \n" + + " (mask.a - glow_max) / (mask_blend_fg - glow_max) ) : \n" + + " ghost_rgb;\n" + + "}\n"; + + // Background model mean update shader. Skews the current model mean toward the most recent pixel + // value for a pixel, weighted by the learning rate and by whether the pixel is classified as + // foreground or background. + // Inputs: + // tex_sampler_0: Mip-map for foreground (live) video frame. + // tex_sampler_1: Background mean mask. + // tex_sampler_2: Foreground/background mask. + // subsample_level: Level on foreground frame's mip-map. + private static final String mUpdateBgModelMeanShader = + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform sampler2D tex_sampler_2;\n" + + "uniform float subsample_level;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 fg_rgb = texture2D(tex_sampler_0, v_texcoord, subsample_level);\n" + + " vec4 fg = coeff_yuv * vec4(fg_rgb.rgb, 1.);\n" + + " vec4 mean = texture2D(tex_sampler_1, v_texcoord);\n" + + " vec4 mask = texture2D(tex_sampler_2, v_texcoord, \n" + + " " + MASK_SMOOTH_EXPONENT + ");\n" + + "\n" + + " float alpha = local_adapt_rate(mask.a);\n" + + " vec4 new_mean = mix(mean, fg, alpha);\n" + + " gl_FragColor = new_mean;\n" + + "}\n"; + + // Background model variance update shader. Skews the current model variance toward the most + // recent variance for the pixel, weighted by the learning rate and by whether the pixel is + // classified as foreground or background. + // Inputs: + // tex_sampler_0: Mip-map for foreground (live) video frame. + // tex_sampler_1: Background mean mask. + // tex_sampler_2: Background variance mask. + // tex_sampler_3: Foreground/background mask. + // subsample_level: Level on foreground frame's mip-map. + // TODO: to improve efficiency, use single mark for mean + variance, then merge this into + // mUpdateBgModelMeanShader. + private static final String mUpdateBgModelVarianceShader = + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform sampler2D tex_sampler_2;\n" + + "uniform sampler2D tex_sampler_3;\n" + + "uniform float subsample_level;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 fg_rgb = texture2D(tex_sampler_0, v_texcoord, subsample_level);\n" + + " vec4 fg = coeff_yuv * vec4(fg_rgb.rgb, 1.);\n" + + " vec4 mean = texture2D(tex_sampler_1, v_texcoord);\n" + + " vec4 variance = inv_var_scale * texture2D(tex_sampler_2, v_texcoord);\n" + + " vec4 mask = texture2D(tex_sampler_3, v_texcoord, \n" + + " " + MASK_SMOOTH_EXPONENT + ");\n" + + "\n" + + " float alpha = local_adapt_rate(mask.a);\n" + + " vec4 cur_variance = (fg-mean)*(fg-mean);\n" + + " vec4 new_variance = mix(variance, cur_variance, alpha);\n" + + " new_variance = max(new_variance, vec4(min_variance));\n" + + " gl_FragColor = var_scale * new_variance;\n" + + "}\n"; + + // Background verification shader. Skews the current background verification mask towards the + // most recent frame, weighted by the learning rate. + private static final String mMaskVerifyShader = + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform float verify_rate;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 lastmask = texture2D(tex_sampler_0, v_texcoord);\n" + + " vec4 mask = texture2D(tex_sampler_1, v_texcoord);\n" + + " float newmask = mix(lastmask.a, mask.a, verify_rate);\n" + + " gl_FragColor = vec4(0., 0., 0., newmask);\n" + + "}\n"; + + /** Shader program objects */ + + private ShaderProgram mBgDistProgram; + private ShaderProgram mBgMaskProgram; + private ShaderProgram mBgSubtractProgram; + private ShaderProgram mBgUpdateMeanProgram; + private ShaderProgram mBgUpdateVarianceProgram; + private ShaderProgram mCopyOutProgram; + private ShaderProgram mAutomaticWhiteBalanceProgram; + private ShaderProgram mMaskVerifyProgram; + private ShaderProgram copyShaderProgram; + + /** Background model storage */ + + private boolean mPingPong; + private GLFrame mBgMean[]; + private GLFrame mBgVariance[]; + private GLFrame mMaskVerify[]; + private GLFrame mDistance; + private GLFrame mAutoWB; + private GLFrame mMask; + private GLFrame mVideoInput; + private GLFrame mBgInput; + private GLFrame mMaskAverage; + + /** Overall filter state */ + + private boolean isOpen; + private int mFrameCount; + private boolean mStartLearning; + private boolean mBackgroundFitModeChanged; + private float mRelativeAspect; + private int mPyramidDepth; + private int mSubsampleLevel; + + /** Learning listener object */ + + public interface LearningDoneListener { + public void onLearningDone(BackDropperFilter filter); + } + + /** Public Filter methods */ + + public BackDropperFilter(String name) { + super(name); + + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + @Override + public void setupPorts() { + // Inputs. + // TODO: Target should be GPU, but relaxed for now. + FrameFormat imageFormat = ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_UNSPECIFIED); + for (String inputName : mInputNames) { + addMaskedInputPort(inputName, imageFormat); + } + // Normal outputs + for (String outputName : mOutputNames) { + addOutputBasedOnInput(outputName, "video"); + } + + // Debug outputs + if (mProvideDebugOutputs) { + for (String outputName : mDebugOutputNames) { + addOutputBasedOnInput(outputName, "video"); + } + } + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + // Create memory format based on video input. + MutableFrameFormat format = inputFormat.mutableCopy(); + // Is this a debug output port? If so, leave dimensions unspecified. + if (!Arrays.asList(mOutputNames).contains(portName)) { + format.setDimensions(FrameFormat.SIZE_UNSPECIFIED, FrameFormat.SIZE_UNSPECIFIED); + } + return format; + } + + private boolean createMemoryFormat(FrameFormat inputFormat) { + // We can't resize because that would require re-learning. + if (mMemoryFormat != null) { + return false; + } + + if (inputFormat.getWidth() == FrameFormat.SIZE_UNSPECIFIED || + inputFormat.getHeight() == FrameFormat.SIZE_UNSPECIFIED) { + throw new RuntimeException("Attempting to process input frame with unknown size"); + } + + mMaskFormat = inputFormat.mutableCopy(); + int maskWidth = (int)Math.pow(2, mMaskWidthExp); + int maskHeight = (int)Math.pow(2, mMaskHeightExp); + mMaskFormat.setDimensions(maskWidth, maskHeight); + + mPyramidDepth = Math.max(mMaskWidthExp, mMaskHeightExp); + mMemoryFormat = mMaskFormat.mutableCopy(); + int widthExp = Math.max(mMaskWidthExp, pyramidLevel(inputFormat.getWidth())); + int heightExp = Math.max(mMaskHeightExp, pyramidLevel(inputFormat.getHeight())); + mPyramidDepth = Math.max(widthExp, heightExp); + int memWidth = Math.max(maskWidth, (int)Math.pow(2, widthExp)); + int memHeight = Math.max(maskHeight, (int)Math.pow(2, heightExp)); + mMemoryFormat.setDimensions(memWidth, memHeight); + mSubsampleLevel = mPyramidDepth - Math.max(mMaskWidthExp, mMaskHeightExp); + + if (mLogVerbose) { + Log.v(TAG, "Mask frames size " + maskWidth + " x " + maskHeight); + Log.v(TAG, "Pyramid levels " + widthExp + " x " + heightExp); + Log.v(TAG, "Memory frames size " + memWidth + " x " + memHeight); + } + + mAverageFormat = inputFormat.mutableCopy(); + mAverageFormat.setDimensions(1,1); + return true; + } + + public void prepare(FilterContext context){ + if (mLogVerbose) Log.v(TAG, "Preparing BackDropperFilter!"); + + mBgMean = new GLFrame[2]; + mBgVariance = new GLFrame[2]; + mMaskVerify = new GLFrame[2]; + copyShaderProgram = ShaderProgram.createIdentity(context); + } + + private void allocateFrames(FrameFormat inputFormat, FilterContext context) { + if (!createMemoryFormat(inputFormat)) { + return; // All set. + } + if (mLogVerbose) Log.v(TAG, "Allocating BackDropperFilter frames"); + + // Create initial background model values + int numBytes = mMaskFormat.getSize(); + byte[] initialBgMean = new byte[numBytes]; + byte[] initialBgVariance = new byte[numBytes]; + byte[] initialMaskVerify = new byte[numBytes]; + for (int i = 0; i < numBytes; i++) { + initialBgMean[i] = (byte)128; + initialBgVariance[i] = (byte)10; + initialMaskVerify[i] = (byte)0; + } + + // Get frames to store background model in + for (int i = 0; i < 2; i++) { + mBgMean[i] = (GLFrame)context.getFrameManager().newFrame(mMaskFormat); + mBgMean[i].setData(initialBgMean, 0, numBytes); + + mBgVariance[i] = (GLFrame)context.getFrameManager().newFrame(mMaskFormat); + mBgVariance[i].setData(initialBgVariance, 0, numBytes); + + mMaskVerify[i] = (GLFrame)context.getFrameManager().newFrame(mMaskFormat); + mMaskVerify[i].setData(initialMaskVerify, 0, numBytes); + } + + // Get frames to store other textures in + if (mLogVerbose) Log.v(TAG, "Done allocating texture for Mean and Variance objects!"); + + mDistance = (GLFrame)context.getFrameManager().newFrame(mMaskFormat); + mMask = (GLFrame)context.getFrameManager().newFrame(mMaskFormat); + mAutoWB = (GLFrame)context.getFrameManager().newFrame(mAverageFormat); + mVideoInput = (GLFrame)context.getFrameManager().newFrame(mMemoryFormat); + mBgInput = (GLFrame)context.getFrameManager().newFrame(mMemoryFormat); + mMaskAverage = (GLFrame)context.getFrameManager().newFrame(mAverageFormat); + + // Create shader programs + mBgDistProgram = new ShaderProgram(context, mSharedUtilShader + mBgDistanceShader); + mBgDistProgram.setHostValue("subsample_level", (float)mSubsampleLevel); + + mBgMaskProgram = new ShaderProgram(context, mSharedUtilShader + mBgMaskShader); + mBgMaskProgram.setHostValue("accept_variance", mAcceptStddev * mAcceptStddev); + float[] yuvWeights = { mLumScale, mChromaScale }; + mBgMaskProgram.setHostValue("yuv_weights", yuvWeights ); + mBgMaskProgram.setHostValue("scale_lrg", mHierarchyLrgScale); + mBgMaskProgram.setHostValue("scale_mid", mHierarchyMidScale); + mBgMaskProgram.setHostValue("scale_sml", mHierarchySmlScale); + mBgMaskProgram.setHostValue("exp_lrg", (float)(mSubsampleLevel + mHierarchyLrgExp)); + mBgMaskProgram.setHostValue("exp_mid", (float)(mSubsampleLevel + mHierarchyMidExp)); + mBgMaskProgram.setHostValue("exp_sml", (float)(mSubsampleLevel + mHierarchySmlExp)); + + if (mUseTheForce) { + mBgSubtractProgram = new ShaderProgram(context, mSharedUtilShader + mBgSubtractShader + mBgSubtractForceShader); + } else { + mBgSubtractProgram = new ShaderProgram(context, mSharedUtilShader + mBgSubtractShader + "}\n"); + } + mBgSubtractProgram.setHostValue("bg_fit_transform", DEFAULT_BG_FIT_TRANSFORM); + mBgSubtractProgram.setHostValue("mask_blend_bg", mMaskBg); + mBgSubtractProgram.setHostValue("mask_blend_fg", mMaskFg); + mBgSubtractProgram.setHostValue("exposure_change", mExposureChange); + mBgSubtractProgram.setHostValue("whitebalanceblue_change", mWhiteBalanceBlueChange); + mBgSubtractProgram.setHostValue("whitebalancered_change", mWhiteBalanceRedChange); + + + mBgUpdateMeanProgram = new ShaderProgram(context, mSharedUtilShader + mUpdateBgModelMeanShader); + mBgUpdateMeanProgram.setHostValue("subsample_level", (float)mSubsampleLevel); + + mBgUpdateVarianceProgram = new ShaderProgram(context, mSharedUtilShader + mUpdateBgModelVarianceShader); + mBgUpdateVarianceProgram.setHostValue("subsample_level", (float)mSubsampleLevel); + + mCopyOutProgram = ShaderProgram.createIdentity(context); + + mAutomaticWhiteBalanceProgram = new ShaderProgram(context, mSharedUtilShader + mAutomaticWhiteBalance); + mAutomaticWhiteBalanceProgram.setHostValue("pyramid_depth", (float)mPyramidDepth); + mAutomaticWhiteBalanceProgram.setHostValue("autowb_toggle", mAutoWBToggle); + + mMaskVerifyProgram = new ShaderProgram(context, mSharedUtilShader + mMaskVerifyShader); + mMaskVerifyProgram.setHostValue("verify_rate", mVerifyRate); + + if (mLogVerbose) Log.v(TAG, "Shader width set to " + mMemoryFormat.getWidth()); + + mRelativeAspect = 1.f; + + mFrameCount = 0; + mStartLearning = true; + } + + public void process(FilterContext context) { + // Grab inputs and ready intermediate frames and outputs. + Frame video = pullInput("video"); + Frame background = pullInput("background"); + allocateFrames(video.getFormat(), context); + + // Update learning rate after initial learning period + if (mStartLearning) { + if (mLogVerbose) Log.v(TAG, "Starting learning"); + mBgUpdateMeanProgram.setHostValue("bg_adapt_rate", mAdaptRateLearning); + mBgUpdateMeanProgram.setHostValue("fg_adapt_rate", mAdaptRateLearning); + mBgUpdateVarianceProgram.setHostValue("bg_adapt_rate", mAdaptRateLearning); + mBgUpdateVarianceProgram.setHostValue("fg_adapt_rate", mAdaptRateLearning); + mFrameCount = 0; + mStartLearning = false; + } + + // Select correct pingpong buffers + int inputIndex = mPingPong ? 0 : 1; + int outputIndex = mPingPong ? 1 : 0; + mPingPong = !mPingPong; + + // Check relative aspect ratios + updateBgScaling(video, background, mBackgroundFitModeChanged); + mBackgroundFitModeChanged = false; + + // Make copies for input frames to GLFrames + + copyShaderProgram.process(video, mVideoInput); + copyShaderProgram.process(background, mBgInput); + + mVideoInput.generateMipMap(); + mVideoInput.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR_MIPMAP_NEAREST); + + mBgInput.generateMipMap(); + mBgInput.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR_MIPMAP_NEAREST); + + // Process shaders + Frame[] distInputs = { mVideoInput, mBgMean[inputIndex], mBgVariance[inputIndex] }; + mBgDistProgram.process(distInputs, mDistance); + mDistance.generateMipMap(); + mDistance.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR_MIPMAP_NEAREST); + + mBgMaskProgram.process(mDistance, mMask); + mMask.generateMipMap(); + mMask.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR_MIPMAP_NEAREST); + + Frame[] autoWBInputs = { mVideoInput, mBgInput }; + mAutomaticWhiteBalanceProgram.process(autoWBInputs, mAutoWB); + + if (mFrameCount <= mLearningDuration) { + // During learning + pushOutput("video", video); + + if (mFrameCount == mLearningDuration - mLearningVerifyDuration) { + copyShaderProgram.process(mMask, mMaskVerify[outputIndex]); + + mBgUpdateMeanProgram.setHostValue("bg_adapt_rate", mAdaptRateBg); + mBgUpdateMeanProgram.setHostValue("fg_adapt_rate", mAdaptRateFg); + mBgUpdateVarianceProgram.setHostValue("bg_adapt_rate", mAdaptRateBg); + mBgUpdateVarianceProgram.setHostValue("fg_adapt_rate", mAdaptRateFg); + + + } else if (mFrameCount > mLearningDuration - mLearningVerifyDuration) { + // In the learning verification stage, compute background masks and a weighted average + // with weights grow exponentially with time + Frame[] maskVerifyInputs = {mMaskVerify[inputIndex], mMask}; + mMaskVerifyProgram.process(maskVerifyInputs, mMaskVerify[outputIndex]); + mMaskVerify[outputIndex].generateMipMap(); + mMaskVerify[outputIndex].setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR_MIPMAP_NEAREST); + } + + if (mFrameCount == mLearningDuration) { + // In the last verification frame, verify if the verification mask is almost blank + // If not, restart learning + copyShaderProgram.process(mMaskVerify[outputIndex], mMaskAverage); + ByteBuffer mMaskAverageByteBuffer = mMaskAverage.getData(); + byte[] mask_average = mMaskAverageByteBuffer.array(); + int bi = (int)(mask_average[3] & 0xFF); + if (mLogVerbose) Log.v(TAG, String.format("Mask_average is %d", bi)); + + if (bi >= DEFAULT_LEARNING_DONE_THRESHOLD) { + mStartLearning = true; // Restart learning + } else { + if (mLogVerbose) Log.v(TAG, "Learning done"); + if (mLearningDoneListener != null) { + mLearningDoneListener.onLearningDone(this); + } + } + } + } else { + Frame output = context.getFrameManager().newFrame(video.getFormat()); + Frame[] subtractInputs = { video, background, mMask, mAutoWB }; + mBgSubtractProgram.process(subtractInputs, output); + pushOutput("video", output); + output.release(); + } + + // Compute mean and variance of the background + if (mFrameCount < mLearningDuration - mLearningVerifyDuration || + mAdaptRateBg > 0.0 || mAdaptRateFg > 0.0) { + Frame[] meanUpdateInputs = { mVideoInput, mBgMean[inputIndex], mMask }; + mBgUpdateMeanProgram.process(meanUpdateInputs, mBgMean[outputIndex]); + mBgMean[outputIndex].generateMipMap(); + mBgMean[outputIndex].setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR_MIPMAP_NEAREST); + + Frame[] varianceUpdateInputs = { + mVideoInput, mBgMean[inputIndex], mBgVariance[inputIndex], mMask + }; + mBgUpdateVarianceProgram.process(varianceUpdateInputs, mBgVariance[outputIndex]); + mBgVariance[outputIndex].generateMipMap(); + mBgVariance[outputIndex].setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR_MIPMAP_NEAREST); + } + + // Provide debug output to two smaller viewers + if (mProvideDebugOutputs) { + Frame dbg1 = context.getFrameManager().newFrame(video.getFormat()); + mCopyOutProgram.process(video, dbg1); + pushOutput("debug1", dbg1); + dbg1.release(); + + Frame dbg2 = context.getFrameManager().newFrame(mMemoryFormat); + mCopyOutProgram.process(mMask, dbg2); + pushOutput("debug2", dbg2); + dbg2.release(); + } + + mFrameCount++; + + if (mLogVerbose) { + if (mFrameCount % 30 == 0) { + if (startTime == -1) { + context.getGLEnvironment().activate(); + GLES20.glFinish(); + startTime = SystemClock.elapsedRealtime(); + } else { + context.getGLEnvironment().activate(); + GLES20.glFinish(); + long endTime = SystemClock.elapsedRealtime(); + Log.v(TAG, "Avg. frame duration: " + String.format("%.2f",(endTime-startTime)/30.) + + " ms. Avg. fps: " + String.format("%.2f", 1000./((endTime-startTime)/30.)) ); + startTime = endTime; + } + } + } + } + + private long startTime = -1; + + public void close(FilterContext context) { + if (mMemoryFormat == null) { + return; + } + + if (mLogVerbose) Log.v(TAG, "Filter Closing!"); + for (int i = 0; i < 2; i++) { + mBgMean[i].release(); + mBgVariance[i].release(); + mMaskVerify[i].release(); + } + mDistance.release(); + mMask.release(); + mAutoWB.release(); + mVideoInput.release(); + mBgInput.release(); + mMaskAverage.release(); + + mMemoryFormat = null; + } + + // Relearn background model + synchronized public void relearn() { + // Let the processing thread know about learning restart + mStartLearning = true; + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + // TODO: Many of these can be made ProgramPorts! + if (name.equals("backgroundFitMode")) { + mBackgroundFitModeChanged = true; + } else if (name.equals("acceptStddev")) { + mBgMaskProgram.setHostValue("accept_variance", mAcceptStddev * mAcceptStddev); + } else if (name.equals("hierLrgScale")) { + mBgMaskProgram.setHostValue("scale_lrg", mHierarchyLrgScale); + } else if (name.equals("hierMidScale")) { + mBgMaskProgram.setHostValue("scale_mid", mHierarchyMidScale); + } else if (name.equals("hierSmlScale")) { + mBgMaskProgram.setHostValue("scale_sml", mHierarchySmlScale); + } else if (name.equals("hierLrgExp")) { + mBgMaskProgram.setHostValue("exp_lrg", (float)(mSubsampleLevel + mHierarchyLrgExp)); + } else if (name.equals("hierMidExp")) { + mBgMaskProgram.setHostValue("exp_mid", (float)(mSubsampleLevel + mHierarchyMidExp)); + } else if (name.equals("hierSmlExp")) { + mBgMaskProgram.setHostValue("exp_sml", (float)(mSubsampleLevel + mHierarchySmlExp)); + } else if (name.equals("lumScale") || name.equals("chromaScale")) { + float[] yuvWeights = { mLumScale, mChromaScale }; + mBgMaskProgram.setHostValue("yuv_weights", yuvWeights ); + } else if (name.equals("maskBg")) { + mBgSubtractProgram.setHostValue("mask_blend_bg", mMaskBg); + } else if (name.equals("maskFg")) { + mBgSubtractProgram.setHostValue("mask_blend_fg", mMaskFg); + } else if (name.equals("exposureChange")) { + mBgSubtractProgram.setHostValue("exposure_change", mExposureChange); + } else if (name.equals("whitebalanceredChange")) { + mBgSubtractProgram.setHostValue("whitebalancered_change", mWhiteBalanceRedChange); + } else if (name.equals("whitebalanceblueChange")) { + mBgSubtractProgram.setHostValue("whitebalanceblue_change", mWhiteBalanceBlueChange); + } else if (name.equals("autowbToggle")){ + mAutomaticWhiteBalanceProgram.setHostValue("autowb_toggle", mAutoWBToggle); + } + } + + private void updateBgScaling(Frame video, Frame background, boolean fitModeChanged) { + float foregroundAspect = (float)video.getFormat().getWidth() / video.getFormat().getHeight(); + float backgroundAspect = (float)background.getFormat().getWidth() / background.getFormat().getHeight(); + float currentRelativeAspect = foregroundAspect/backgroundAspect; + if (currentRelativeAspect != mRelativeAspect || fitModeChanged) { + mRelativeAspect = currentRelativeAspect; + float xMin = 0.f, xWidth = 1.f, yMin = 0.f, yWidth = 1.f; + switch (mBackgroundFitMode) { + case BACKGROUND_STRETCH: + // Just map 1:1 + break; + case BACKGROUND_FIT: + if (mRelativeAspect > 1.0f) { + // Foreground is wider than background, scale down + // background in X + xMin = 0.5f - 0.5f * mRelativeAspect; + xWidth = 1.f * mRelativeAspect; + } else { + // Foreground is taller than background, scale down + // background in Y + yMin = 0.5f - 0.5f / mRelativeAspect; + yWidth = 1 / mRelativeAspect; + } + break; + case BACKGROUND_FILL_CROP: + if (mRelativeAspect > 1.0f) { + // Foreground is wider than background, crop + // background in Y + yMin = 0.5f - 0.5f / mRelativeAspect; + yWidth = 1.f / mRelativeAspect; + } else { + // Foreground is taller than background, crop + // background in X + xMin = 0.5f - 0.5f * mRelativeAspect; + xWidth = mRelativeAspect; + } + break; + } + // If mirroring is required (for ex. the camera mirrors the preview + // in the front camera) + // TODO: Backdropper does not attempt to apply any other transformation + // than just flipping. However, in the current state, it's "x-axis" is always aligned + // with the Camera's width. Hence, we need to define the mirroring based on the camera + // orientation. In the future, a cleaner design would be to cast away all the rotation + // in a separate place. + if (mMirrorBg) { + if (mLogVerbose) Log.v(TAG, "Mirroring the background!"); + // Mirroring in portrait + if (mOrientation == 0 || mOrientation == 180) { + xWidth = -xWidth; + xMin = 1.0f - xMin; + } else { + // Mirroring in landscape + yWidth = -yWidth; + yMin = 1.0f - yMin; + } + } + if (mLogVerbose) Log.v(TAG, "bgTransform: xMin, yMin, xWidth, yWidth : " + + xMin + ", " + yMin + ", " + xWidth + ", " + yWidth + + ", mRelAspRatio = " + mRelativeAspect); + // The following matrix is the transpose of the actual matrix + float[] bgTransform = {xWidth, 0.f, 0.f, + 0.f, yWidth, 0.f, + xMin, yMin, 1.f}; + mBgSubtractProgram.setHostValue("bg_fit_transform", bgTransform); + } + } + + private int pyramidLevel(int size) { + return (int)Math.floor(Math.log10(size) / Math.log10(2)) - 1; + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java b/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java new file mode 100644 index 0000000..3657d8a --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java @@ -0,0 +1,469 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.videosink; + +import android.content.Context; +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.GLFrame; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.filterfw.geometry.Point; +import android.filterfw.geometry.Quad; +import android.os.ConditionVariable; +import android.media.MediaRecorder; +import android.media.CamcorderProfile; +import android.filterfw.core.GLEnvironment; + +import java.io.IOException; +import java.io.FileDescriptor; +import java.util.List; +import java.util.Set; + +import android.util.Log; + +/** @hide */ +public class MediaEncoderFilter extends Filter { + + /** User-visible parameters */ + + /** Recording state. When set to false, recording will stop, or will not + * start if not yet running the graph. Instead, frames are simply ignored. + * When switched back to true, recording will restart. This allows a single + * graph to both provide preview and to record video. If this is false, + * recording settings can be updated while the graph is running. + */ + @GenerateFieldPort(name = "recording", hasDefault = true) + private boolean mRecording = true; + + /** Filename to save the output. */ + @GenerateFieldPort(name = "outputFile", hasDefault = true) + private String mOutputFile = new String("/sdcard/MediaEncoderOut.mp4"); + + /** File Descriptor to save the output. */ + @GenerateFieldPort(name = "outputFileDescriptor", hasDefault = true) + private FileDescriptor mFd = null; + + /** Input audio source. If not set, no audio will be recorded. + * Select from the values in MediaRecorder.AudioSource + */ + @GenerateFieldPort(name = "audioSource", hasDefault = true) + private int mAudioSource = NO_AUDIO_SOURCE; + + /** Media recorder info listener, which needs to implement + * MediaRecorder.OnInfoListener. Set this to receive notifications about + * recording events. + */ + @GenerateFieldPort(name = "infoListener", hasDefault = true) + private MediaRecorder.OnInfoListener mInfoListener = null; + + /** Media recorder error listener, which needs to implement + * MediaRecorder.OnErrorListener. Set this to receive notifications about + * recording errors. + */ + @GenerateFieldPort(name = "errorListener", hasDefault = true) + private MediaRecorder.OnErrorListener mErrorListener = null; + + /** Media recording done callback, which needs to implement OnRecordingDoneListener. + * Set this to finalize media upon completion of media recording. + */ + @GenerateFieldPort(name = "recordingDoneListener", hasDefault = true) + private OnRecordingDoneListener mRecordingDoneListener = null; + + /** Orientation hint. Used for indicating proper video playback orientation. + * Units are in degrees of clockwise rotation, valid values are (0, 90, 180, + * 270). + */ + @GenerateFieldPort(name = "orientationHint", hasDefault = true) + private int mOrientationHint = 0; + + /** Camcorder profile to use. Select from the profiles available in + * android.media.CamcorderProfile. If this field is set, it overrides + * settings to width, height, framerate, outputFormat, and videoEncoder. + */ + @GenerateFieldPort(name = "recordingProfile", hasDefault = true) + private CamcorderProfile mProfile = null; + + /** Frame width to be encoded, defaults to 320. + * Actual received frame size has to match this */ + @GenerateFieldPort(name = "width", hasDefault = true) + private int mWidth = 320; + + /** Frame height to to be encoded, defaults to 240. + * Actual received frame size has to match */ + @GenerateFieldPort(name = "height", hasDefault = true) + private int mHeight = 240; + + /** Stream framerate to encode the frames at. + * By default, frames are encoded at 30 FPS*/ + @GenerateFieldPort(name = "framerate", hasDefault = true) + private int mFps = 30; + + /** The output format to encode the frames in. + * Choose an output format from the options in + * android.media.MediaRecorder.OutputFormat */ + @GenerateFieldPort(name = "outputFormat", hasDefault = true) + private int mOutputFormat = MediaRecorder.OutputFormat.MPEG_4; + + /** The videoencoder to encode the frames with. + * Choose a videoencoder from the options in + * android.media.MediaRecorder.VideoEncoder */ + @GenerateFieldPort(name = "videoEncoder", hasDefault = true) + private int mVideoEncoder = MediaRecorder.VideoEncoder.H264; + + /** The input region to read from the frame. The corners of this quad are + * mapped to the output rectangle. The input frame ranges from (0,0)-(1,1), + * top-left to bottom-right. The corners of the quad are specified in the + * order bottom-left, bottom-right, top-left, top-right. + */ + @GenerateFieldPort(name = "inputRegion", hasDefault = true) + private Quad mSourceRegion; + + /** The maximum filesize (in bytes) of the recording session. + * By default, it will be 0 and will be passed on to the MediaRecorder. + * If the limit is zero or negative, MediaRecorder will disable the limit*/ + @GenerateFieldPort(name = "maxFileSize", hasDefault = true) + private long mMaxFileSize = 0; + + /** The maximum duration (in milliseconds) of the recording session. + * By default, it will be 0 and will be passed on to the MediaRecorder. + * If the limit is zero or negative, MediaRecorder will record indefinitely*/ + @GenerateFieldPort(name = "maxDurationMs", hasDefault = true) + private int mMaxDurationMs = 0; + + /** TimeLapse Interval between frames. + * By default, it will be 0. Whether the recording is timelapsed + * is inferred based on its value being greater than 0 */ + @GenerateFieldPort(name = "timelapseRecordingIntervalUs", hasDefault = true) + private long mTimeBetweenTimeLapseFrameCaptureUs = 0; + + // End of user visible parameters + + private static final int NO_AUDIO_SOURCE = -1; + + private int mSurfaceId; + private ShaderProgram mProgram; + private GLFrame mScreen; + + private boolean mRecordingActive = false; + private long mTimestampNs = 0; + private long mLastTimeLapseFrameRealTimestampNs = 0; + private int mNumFramesEncoded = 0; + // Used to indicate whether recording is timelapsed. + // Inferred based on (mTimeBetweenTimeLapseFrameCaptureUs > 0) + private boolean mCaptureTimeLapse = false; + + private boolean mLogVerbose; + private static final String TAG = "MediaEncoderFilter"; + + // Our hook to the encoder + private MediaRecorder mMediaRecorder; + + /** Callback to be called when media recording completes. */ + + public interface OnRecordingDoneListener { + public void onRecordingDone(); + } + + public MediaEncoderFilter(String name) { + super(name); + Point bl = new Point(0, 0); + Point br = new Point(1, 0); + Point tl = new Point(0, 1); + Point tr = new Point(1, 1); + mSourceRegion = new Quad(bl, br, tl, tr); + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + @Override + public void setupPorts() { + // Add input port- will accept RGBA GLFrames + addMaskedInputPort("videoframe", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Port " + name + " has been updated"); + if (name.equals("recording")) return; + if (name.equals("inputRegion")) { + if (isOpen()) updateSourceRegion(); + return; + } + // TODO: Not sure if it is possible to update the maxFileSize + // when the recording is going on. For now, not doing that. + if (isOpen() && mRecordingActive) { + throw new RuntimeException("Cannot change recording parameters" + + " when the filter is recording!"); + } + } + + private void updateSourceRegion() { + // Flip source quad to map to OpenGL origin + Quad flippedRegion = new Quad(); + flippedRegion.p0 = mSourceRegion.p2; + flippedRegion.p1 = mSourceRegion.p3; + flippedRegion.p2 = mSourceRegion.p0; + flippedRegion.p3 = mSourceRegion.p1; + mProgram.setSourceRegion(flippedRegion); + } + + // update the MediaRecorderParams based on the variables. + // These have to be in certain order as per the MediaRecorder + // documentation + private void updateMediaRecorderParams() { + mCaptureTimeLapse = mTimeBetweenTimeLapseFrameCaptureUs > 0; + final int GRALLOC_BUFFER = 2; + mMediaRecorder.setVideoSource(GRALLOC_BUFFER); + if (!mCaptureTimeLapse && (mAudioSource != NO_AUDIO_SOURCE)) { + mMediaRecorder.setAudioSource(mAudioSource); + } + if (mProfile != null) { + mMediaRecorder.setProfile(mProfile); + mFps = mProfile.videoFrameRate; + } else { + mMediaRecorder.setOutputFormat(mOutputFormat); + mMediaRecorder.setVideoEncoder(mVideoEncoder); + mMediaRecorder.setVideoSize(mWidth, mHeight); + mMediaRecorder.setVideoFrameRate(mFps); + } + mMediaRecorder.setOrientationHint(mOrientationHint); + mMediaRecorder.setOnInfoListener(mInfoListener); + mMediaRecorder.setOnErrorListener(mErrorListener); + if (mFd != null) { + mMediaRecorder.setOutputFile(mFd); + } else { + mMediaRecorder.setOutputFile(mOutputFile); + } + try { + mMediaRecorder.setMaxFileSize(mMaxFileSize); + } catch (Exception e) { + // Following the logic in VideoCamera.java (in Camera app) + // We are going to ignore failure of setMaxFileSize here, as + // a) The composer selected may simply not support it, or + // b) The underlying media framework may not handle 64-bit range + // on the size restriction. + Log.w(TAG, "Setting maxFileSize on MediaRecorder unsuccessful! " + + e.getMessage()); + } + mMediaRecorder.setMaxDuration(mMaxDurationMs); + } + + @Override + public void prepare(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Preparing"); + + mProgram = ShaderProgram.createIdentity(context); + + mRecordingActive = false; + } + + @Override + public void open(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Opening"); + updateSourceRegion(); + if (mRecording) startRecording(context); + } + + private void startRecording(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Starting recording"); + + // Create a frame representing the screen + MutableFrameFormat screenFormat = new MutableFrameFormat( + FrameFormat.TYPE_BYTE, FrameFormat.TARGET_GPU); + screenFormat.setBytesPerSample(4); + + int width, height; + if (mProfile != null) { + width = mProfile.videoFrameWidth; + height = mProfile.videoFrameHeight; + } else { + width = mWidth; + height = mHeight; + } + screenFormat.setDimensions(width, height); + mScreen = (GLFrame)context.getFrameManager().newBoundFrame( + screenFormat, GLFrame.EXISTING_FBO_BINDING, 0); + + // Initialize the media recorder + + mMediaRecorder = new MediaRecorder(); + updateMediaRecorderParams(); + + try { + mMediaRecorder.prepare(); + } catch (IllegalStateException e) { + throw e; + } catch (IOException e) { + throw new RuntimeException("IOException in" + + "MediaRecorder.prepare()!", e); + } catch (Exception e) { + throw new RuntimeException("Unknown Exception in" + + "MediaRecorder.prepare()!", e); + } + // Make sure start() is called before trying to + // register the surface. The native window handle needed to create + // the surface is initiated in start() + mMediaRecorder.start(); + if (mLogVerbose) Log.v(TAG, "Open: registering surface from Mediarecorder"); + mSurfaceId = context.getGLEnvironment(). + registerSurfaceFromMediaRecorder(mMediaRecorder); + mNumFramesEncoded = 0; + mRecordingActive = true; + } + + public boolean skipFrameAndModifyTimestamp(long timestampNs) { + // first frame- encode. Don't skip + if (mNumFramesEncoded == 0) { + mLastTimeLapseFrameRealTimestampNs = timestampNs; + mTimestampNs = timestampNs; + if (mLogVerbose) Log.v(TAG, "timelapse: FIRST frame, last real t= " + + mLastTimeLapseFrameRealTimestampNs + + ", setting t = " + mTimestampNs ); + return false; + } + + // Workaround to bypass the first 2 input frames for skipping. + // The first 2 output frames from the encoder are: decoder specific info and + // the compressed video frame data for the first input video frame. + if (mNumFramesEncoded >= 2 && timestampNs < + (mLastTimeLapseFrameRealTimestampNs + 1000L * mTimeBetweenTimeLapseFrameCaptureUs)) { + // If 2 frames have been already encoded, + // Skip all frames from last encoded frame until + // sufficient time (mTimeBetweenTimeLapseFrameCaptureUs) has passed. + if (mLogVerbose) Log.v(TAG, "timelapse: skipping intermediate frame"); + return true; + } else { + // Desired frame has arrived after mTimeBetweenTimeLapseFrameCaptureUs time: + // - Reset mLastTimeLapseFrameRealTimestampNs to current time. + // - Artificially modify timestampNs to be one frame time (1/framerate) ahead + // of the last encoded frame's time stamp. + if (mLogVerbose) Log.v(TAG, "timelapse: encoding frame, Timestamp t = " + timestampNs + + ", last real t= " + mLastTimeLapseFrameRealTimestampNs + + ", interval = " + mTimeBetweenTimeLapseFrameCaptureUs); + mLastTimeLapseFrameRealTimestampNs = timestampNs; + mTimestampNs = mTimestampNs + (1000000000L / (long)mFps); + if (mLogVerbose) Log.v(TAG, "timelapse: encoding frame, setting t = " + + mTimestampNs + ", delta t = " + (1000000000L / (long)mFps) + + ", fps = " + mFps ); + return false; + } + } + + @Override + public void process(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Starting frame processing"); + + GLEnvironment glEnv = context.getGLEnvironment(); + // Get input frame + Frame input = pullInput("videoframe"); + + // Check if recording needs to start + if (!mRecordingActive && mRecording) { + startRecording(context); + } + // Check if recording needs to stop + if (mRecordingActive && !mRecording) { + stopRecording(context); + } + + if (!mRecordingActive) return; + + if (mCaptureTimeLapse) { + if (skipFrameAndModifyTimestamp(input.getTimestamp())) { + return; + } + } else { + mTimestampNs = input.getTimestamp(); + } + + // Activate our surface + glEnv.activateSurfaceWithId(mSurfaceId); + + // Process + mProgram.process(input, mScreen); + + // Set timestamp from input + glEnv.setSurfaceTimestamp(mTimestampNs); + // And swap buffers + glEnv.swapBuffers(); + mNumFramesEncoded++; + if (mLogVerbose) Log.v(TAG, "numFramesEncoded = " + mNumFramesEncoded); + } + + private void stopRecording(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Stopping recording"); + + mRecordingActive = false; + mNumFramesEncoded = 0; + GLEnvironment glEnv = context.getGLEnvironment(); + // The following call will switch the surface_id to 0 + // (thus, calling eglMakeCurrent on surface with id 0) and + // then call eglDestroy on the surface. Hence, this will + // call disconnect the SurfaceMediaSource, which is needed to + // be called before calling Stop on the mediarecorder + if (mLogVerbose) Log.v(TAG, String.format("Unregistering surface %d", mSurfaceId)); + glEnv.unregisterSurfaceId(mSurfaceId); + try { + mMediaRecorder.stop(); + } catch (RuntimeException e) { + throw new MediaRecorderStopException("MediaRecorder.stop() failed!", e); + } + mMediaRecorder.release(); + mMediaRecorder = null; + + mScreen.release(); + mScreen = null; + + // Use an EffectsRecorder callback to forward a media finalization + // call so that it creates the video thumbnail, and whatever else needs + // to be done to finalize media. + if (mRecordingDoneListener != null) { + mRecordingDoneListener.onRecordingDone(); + } + } + + @Override + public void close(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Closing"); + if (mRecordingActive) stopRecording(context); + } + + @Override + public void tearDown(FilterContext context) { + // Release all the resources associated with the MediaRecorder + // and GLFrame members + if (mMediaRecorder != null) { + mMediaRecorder.release(); + } + if (mScreen != null) { + mScreen.release(); + } + + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/videosink/MediaRecorderStopException.java b/media/mca/filterpacks/java/android/filterpacks/videosink/MediaRecorderStopException.java new file mode 100644 index 0000000..dbf9768 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/videosink/MediaRecorderStopException.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.videosink; + +import java.lang.RuntimeException; +import android.util.Log; + +/** @hide **/ +public class MediaRecorderStopException extends RuntimeException { + + private static final String TAG = "MediaRecorderStopException"; + + public MediaRecorderStopException(String msg) { + super(msg); + } + + public MediaRecorderStopException() { + super(); + } + + public MediaRecorderStopException(String msg, Throwable t) { + super(msg, t); + } + + public MediaRecorderStopException(Throwable t) { + super(t); + } +} + diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/CameraSource.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/CameraSource.java new file mode 100644 index 0000000..2c474ab --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/CameraSource.java @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.videosrc; + +import android.content.Context; +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.GLFrame; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.graphics.SurfaceTexture; +import android.hardware.Camera; +import android.os.ConditionVariable; +import android.opengl.Matrix; + +import java.io.IOException; +import java.util.List; +import java.util.Set; + +import android.util.Log; + +/** + * @hide + */ +public class CameraSource extends Filter { + + /** User-visible parameters */ + + /** Camera ID to use for input. Defaults to 0. */ + @GenerateFieldPort(name = "id", hasDefault = true) + private int mCameraId = 0; + + /** Frame width to request from camera. Actual size may not match requested. */ + @GenerateFieldPort(name = "width", hasDefault = true) + private int mWidth = 320; + + /** Frame height to request from camera. Actual size may not match requested. */ + @GenerateFieldPort(name = "height", hasDefault = true) + private int mHeight = 240; + + /** Stream framerate to request from camera. Actual frame rate may not match requested. */ + @GenerateFieldPort(name = "framerate", hasDefault = true) + private int mFps = 30; + + /** Whether the filter should always wait for a new frame from the camera + * before providing output. If set to false, the filter will keep + * outputting the last frame it received from the camera if multiple process + * calls are received before the next update from the Camera. Defaults to true. + */ + @GenerateFinalPort(name = "waitForNewFrame", hasDefault = true) + private boolean mWaitForNewFrame = true; + + private Camera mCamera; + private GLFrame mCameraFrame; + private SurfaceTexture mSurfaceTexture; + private ShaderProgram mFrameExtractor; + private MutableFrameFormat mOutputFormat; + + private float[] mCameraTransform; + private float[] mMappedCoords; + // These default source coordinates perform the necessary flip + // for converting from OpenGL origin to MFF/Bitmap origin. + private static final float[] mSourceCoords = { 0, 1, 0, 1, + 1, 1, 0, 1, + 0, 0, 0, 1, + 1, 0, 0, 1 }; + + private static final int NEWFRAME_TIMEOUT = 100; //ms + private static final int NEWFRAME_TIMEOUT_REPEAT = 10; + + private boolean mNewFrameAvailable; + + private Camera.Parameters mCameraParameters; + + private static final String mFrameShader = + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "uniform samplerExternalOES tex_sampler_0;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(tex_sampler_0, v_texcoord);\n" + + "}\n"; + + private final boolean mLogVerbose; + private static final String TAG = "CameraSource"; + + public CameraSource(String name) { + super(name); + mCameraTransform = new float[16]; + mMappedCoords = new float[16]; + + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + @Override + public void setupPorts() { + // Add input port + addOutputPort("video", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + } + + private void createFormats() { + mOutputFormat = ImageFormat.create(mWidth, mHeight, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + } + + @Override + public void prepare(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Preparing"); + // Compile shader TODO: Move to onGLEnvSomething? + mFrameExtractor = new ShaderProgram(context, mFrameShader); + } + + @Override + public void open(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Opening"); + // Open camera + mCamera = Camera.open(mCameraId); + + // Set parameters + getCameraParameters(); + mCamera.setParameters(mCameraParameters); + + // Create frame formats + createFormats(); + + // Bind it to our camera frame + mCameraFrame = (GLFrame)context.getFrameManager().newBoundFrame(mOutputFormat, + GLFrame.EXTERNAL_TEXTURE, + 0); + mSurfaceTexture = new SurfaceTexture(mCameraFrame.getTextureId()); + try { + mCamera.setPreviewTexture(mSurfaceTexture); + } catch (IOException e) { + throw new RuntimeException("Could not bind camera surface texture: " + + e.getMessage() + "!"); + } + + // Connect SurfaceTexture to callback + mSurfaceTexture.setOnFrameAvailableListener(onCameraFrameAvailableListener); + // Start the preview + mNewFrameAvailable = false; + mCamera.startPreview(); + } + + @Override + public void process(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Processing new frame"); + + if (mWaitForNewFrame) { + int waitCount = 0; + while (!mNewFrameAvailable) { + if (waitCount == NEWFRAME_TIMEOUT_REPEAT) { + throw new RuntimeException("Timeout waiting for new frame"); + } + try { + this.wait(NEWFRAME_TIMEOUT); + } catch (InterruptedException e) { + if (mLogVerbose) Log.v(TAG, "Interrupted while waiting for new frame"); + } + } + mNewFrameAvailable = false; + if (mLogVerbose) Log.v(TAG, "Got new frame"); + } + + mSurfaceTexture.updateTexImage(); + + if (mLogVerbose) Log.v(TAG, "Using frame extractor in thread: " + Thread.currentThread()); + mSurfaceTexture.getTransformMatrix(mCameraTransform); + Matrix.multiplyMM(mMappedCoords, 0, + mCameraTransform, 0, + mSourceCoords, 0); + mFrameExtractor.setSourceRegion(mMappedCoords[0], mMappedCoords[1], + mMappedCoords[4], mMappedCoords[5], + mMappedCoords[8], mMappedCoords[9], + mMappedCoords[12], mMappedCoords[13]); + + Frame output = context.getFrameManager().newFrame(mOutputFormat); + mFrameExtractor.process(mCameraFrame, output); + + long timestamp = mSurfaceTexture.getTimestamp(); + if (mLogVerbose) Log.v(TAG, "Timestamp: " + (timestamp / 1000000000.0) + " s"); + output.setTimestamp(timestamp); + + pushOutput("video", output); + + // Release pushed frame + output.release(); + + if (mLogVerbose) Log.v(TAG, "Done processing new frame"); + } + + @Override + public void close(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Closing"); + + mCamera.release(); + mCamera = null; + mSurfaceTexture.release(); + mSurfaceTexture = null; + } + + @Override + public void tearDown(FilterContext context) { + if (mCameraFrame != null) { + mCameraFrame.release(); + } + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (name.equals("framerate")) { + getCameraParameters(); + int closestRange[] = findClosestFpsRange(mFps, mCameraParameters); + mCameraParameters.setPreviewFpsRange(closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX], + closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]); + mCamera.setParameters(mCameraParameters); + } + } + + synchronized public Camera.Parameters getCameraParameters() { + boolean closeCamera = false; + if (mCameraParameters == null) { + if (mCamera == null) { + mCamera = Camera.open(mCameraId); + closeCamera = true; + } + mCameraParameters = mCamera.getParameters(); + + if (closeCamera) { + mCamera.release(); + mCamera = null; + } + } + + int closestSize[] = findClosestSize(mWidth, mHeight, mCameraParameters); + mWidth = closestSize[0]; + mHeight = closestSize[1]; + mCameraParameters.setPreviewSize(mWidth, mHeight); + + int closestRange[] = findClosestFpsRange(mFps, mCameraParameters); + + mCameraParameters.setPreviewFpsRange(closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX], + closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]); + + return mCameraParameters; + } + + /** Update camera parameters. Image resolution cannot be changed. */ + synchronized public void setCameraParameters(Camera.Parameters params) { + params.setPreviewSize(mWidth, mHeight); + mCameraParameters = params; + if (isOpen()) { + mCamera.setParameters(mCameraParameters); + } + } + + private int[] findClosestSize(int width, int height, Camera.Parameters parameters) { + List<Camera.Size> previewSizes = parameters.getSupportedPreviewSizes(); + int closestWidth = -1; + int closestHeight = -1; + int smallestWidth = previewSizes.get(0).width; + int smallestHeight = previewSizes.get(0).height; + for (Camera.Size size : previewSizes) { + // Best match defined as not being larger in either dimension than + // the requested size, but as close as possible. The below isn't a + // stable selection (reording the size list can give different + // results), but since this is a fallback nicety, that's acceptable. + if ( size.width <= width && + size.height <= height && + size.width >= closestWidth && + size.height >= closestHeight) { + closestWidth = size.width; + closestHeight = size.height; + } + if ( size.width < smallestWidth && + size.height < smallestHeight) { + smallestWidth = size.width; + smallestHeight = size.height; + } + } + if (closestWidth == -1) { + // Requested size is smaller than any listed size; match with smallest possible + closestWidth = smallestWidth; + closestHeight = smallestHeight; + } + + if (mLogVerbose) { + Log.v(TAG, + "Requested resolution: (" + width + ", " + height + + "). Closest match: (" + closestWidth + ", " + + closestHeight + ")."); + } + int[] closestSize = {closestWidth, closestHeight}; + return closestSize; + } + + private int[] findClosestFpsRange(int fps, Camera.Parameters params) { + List<int[]> supportedFpsRanges = params.getSupportedPreviewFpsRange(); + int[] closestRange = supportedFpsRanges.get(0); + for (int[] range : supportedFpsRanges) { + if (range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] < fps*1000 && + range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] > fps*1000 && + range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] > + closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] && + range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] < + closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]) { + closestRange = range; + } + } + if (mLogVerbose) Log.v(TAG, "Requested fps: " + fps + + ".Closest frame rate range: [" + + closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] / 1000. + + "," + + closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] / 1000. + + "]"); + + return closestRange; + } + + private SurfaceTexture.OnFrameAvailableListener onCameraFrameAvailableListener = + new SurfaceTexture.OnFrameAvailableListener() { + @Override + public void onFrameAvailable(SurfaceTexture surfaceTexture) { + if (mLogVerbose) Log.v(TAG, "New frame from camera"); + synchronized(CameraSource.this) { + mNewFrameAvailable = true; + CameraSource.this.notify(); + } + } + }; + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/MediaSource.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/MediaSource.java new file mode 100644 index 0000000..9c40cec --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/MediaSource.java @@ -0,0 +1,567 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.videosrc; + +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.GLFrame; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.graphics.SurfaceTexture; +import android.media.MediaPlayer; +import android.os.ConditionVariable; +import android.opengl.Matrix; +import android.view.Surface; + +import java.io.IOException; +import java.io.FileDescriptor; +import java.lang.IllegalArgumentException; +import java.util.List; +import java.util.Set; + +import android.util.Log; + +/** + * @hide + */ +public class MediaSource extends Filter { + + /** User-visible parameters */ + + /** The source URL for the media source. Can be an http: link to a remote + * resource, or a file: link to a local media file + */ + @GenerateFieldPort(name = "sourceUrl", hasDefault = true) + private String mSourceUrl = ""; + + /** An open asset file descriptor to a local media source. Default is null */ + @GenerateFieldPort(name = "sourceAsset", hasDefault = true) + private AssetFileDescriptor mSourceAsset = null; + + /** Whether the media source is a URL or an asset file descriptor. Defaults + * to false. + */ + @GenerateFieldPort(name = "sourceIsUrl", hasDefault = true) + private boolean mSelectedIsUrl = false; + + /** Whether the filter will always wait for a new video frame, or whether it + * will output an old frame again if a new frame isn't available. Defaults + * to true. + */ + @GenerateFinalPort(name = "waitForNewFrame", hasDefault = true) + private boolean mWaitForNewFrame = true; + + /** Whether the media source should loop automatically or not. Defaults to + * true. + */ + @GenerateFieldPort(name = "loop", hasDefault = true) + private boolean mLooping = true; + + /** Volume control. Currently sound is piped directly to the speakers, so + * this defaults to mute. + */ + @GenerateFieldPort(name = "volume", hasDefault = true) + private float mVolume = 0.f; + + /** Orientation. This controls the output orientation of the video. Valid + * values are 0, 90, 180, 270 + */ + @GenerateFieldPort(name = "orientation", hasDefault = true) + private int mOrientation = 0; + + private MediaPlayer mMediaPlayer; + private GLFrame mMediaFrame; + private SurfaceTexture mSurfaceTexture; + private ShaderProgram mFrameExtractor; + private MutableFrameFormat mOutputFormat; + private int mWidth, mHeight; + + // Total timeouts will be PREP_TIMEOUT*PREP_TIMEOUT_REPEAT + private static final int PREP_TIMEOUT = 100; // ms + private static final int PREP_TIMEOUT_REPEAT = 100; + private static final int NEWFRAME_TIMEOUT = 100; //ms + private static final int NEWFRAME_TIMEOUT_REPEAT = 10; + + // This is an identity shader; not using the default identity + // shader because reading from a SurfaceTexture requires the + // GL_OES_EGL_image_external extension. + private final String mFrameShader = + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "uniform samplerExternalOES tex_sampler_0;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(tex_sampler_0, v_texcoord);\n" + + "}\n"; + + // The following transforms enable rotation of the decoded source. + // These are multiplied with the transform obtained from the + // SurfaceTexture to get the final transform to be set on the media source. + // Currently, given a device orientation, the MediaSource rotates in such a way + // that the source is displayed upright. A particular use case + // is "Background Replacement" feature in the Camera app + // where the MediaSource rotates the source to align with the camera feed and pass it + // on to the backdropper filter. The backdropper only does the blending + // and does not have to do any rotation + // (except for mirroring in case of front camera). + // TODO: Currently the rotations are spread over a bunch of stages in the + // pipeline. A cleaner design + // could be to cast away all the rotation in a separate filter or attach a transform + // to the frame so that MediaSource itself need not know about any rotation. + private static final float[] mSourceCoords_0 = { 1, 1, 0, 1, + 0, 1, 0, 1, + 1, 0, 0, 1, + 0, 0, 0, 1 }; + private static final float[] mSourceCoords_270 = { 0, 1, 0, 1, + 0, 0, 0, 1, + 1, 1, 0, 1, + 1, 0, 0, 1 }; + private static final float[] mSourceCoords_180 = { 0, 0, 0, 1, + 1, 0, 0, 1, + 0, 1, 0, 1, + 1, 1, 0, 1 }; + private static final float[] mSourceCoords_90 = { 1, 0, 0, 1, + 1, 1, 0, 1, + 0, 0, 0, 1, + 0, 1, 0, 1 }; + + private boolean mGotSize; + private boolean mPrepared; + private boolean mPlaying; + private boolean mNewFrameAvailable; + private boolean mOrientationUpdated; + private boolean mPaused; + private boolean mCompleted; + + private final boolean mLogVerbose; + private static final String TAG = "MediaSource"; + + public MediaSource(String name) { + super(name); + mNewFrameAvailable = false; + + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + @Override + public void setupPorts() { + // Add input port + addOutputPort("video", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + } + + private void createFormats() { + mOutputFormat = ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + } + + @Override + protected void prepare(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Preparing MediaSource"); + + mFrameExtractor = new ShaderProgram(context, mFrameShader); + // SurfaceTexture defines (0,0) to be bottom-left. The filter framework + // defines (0,0) as top-left, so do the flip here. + mFrameExtractor.setSourceRect(0, 1, 1, -1); + + createFormats(); + } + + @Override + public void open(FilterContext context) { + if (mLogVerbose) { + Log.v(TAG, "Opening MediaSource"); + if (mSelectedIsUrl) { + Log.v(TAG, "Current URL is " + mSourceUrl); + } else { + Log.v(TAG, "Current source is Asset!"); + } + } + + mMediaFrame = (GLFrame)context.getFrameManager().newBoundFrame( + mOutputFormat, + GLFrame.EXTERNAL_TEXTURE, + 0); + + mSurfaceTexture = new SurfaceTexture(mMediaFrame.getTextureId()); + + if (!setupMediaPlayer(mSelectedIsUrl)) { + throw new RuntimeException("Error setting up MediaPlayer!"); + } + } + + @Override + public void process(FilterContext context) { + // Note: process is synchronized by its caller in the Filter base class + if (mLogVerbose) Log.v(TAG, "Processing new frame"); + + if (mMediaPlayer == null) { + // Something went wrong in initialization or parameter updates + throw new NullPointerException("Unexpected null media player!"); + } + + if (mCompleted) { + // Video playback is done, so close us down + closeOutputPort("video"); + return; + } + + if (!mPlaying) { + int waitCount = 0; + if (mLogVerbose) Log.v(TAG, "Waiting for preparation to complete"); + while (!mGotSize || !mPrepared) { + try { + this.wait(PREP_TIMEOUT); + } catch (InterruptedException e) { + // ignoring + } + if (mCompleted) { + // Video playback is done, so close us down + closeOutputPort("video"); + return; + } + waitCount++; + if (waitCount == PREP_TIMEOUT_REPEAT) { + mMediaPlayer.release(); + throw new RuntimeException("MediaPlayer timed out while preparing!"); + } + } + if (mLogVerbose) Log.v(TAG, "Starting playback"); + mMediaPlayer.start(); + } + + // Use last frame if paused, unless just starting playback, in which case + // we want at least one valid frame before pausing + if (!mPaused || !mPlaying) { + if (mWaitForNewFrame) { + if (mLogVerbose) Log.v(TAG, "Waiting for new frame"); + + int waitCount = 0; + while (!mNewFrameAvailable) { + if (waitCount == NEWFRAME_TIMEOUT_REPEAT) { + if (mCompleted) { + // Video playback is done, so close us down + closeOutputPort("video"); + return; + } else { + throw new RuntimeException("Timeout waiting for new frame!"); + } + } + try { + this.wait(NEWFRAME_TIMEOUT); + } catch (InterruptedException e) { + if (mLogVerbose) Log.v(TAG, "interrupted"); + // ignoring + } + waitCount++; + } + mNewFrameAvailable = false; + if (mLogVerbose) Log.v(TAG, "Got new frame"); + } + + mSurfaceTexture.updateTexImage(); + mOrientationUpdated = true; + } + if (mOrientationUpdated) { + float[] surfaceTransform = new float[16]; + mSurfaceTexture.getTransformMatrix(surfaceTransform); + + float[] sourceCoords = new float[16]; + switch (mOrientation) { + default: + case 0: + Matrix.multiplyMM(sourceCoords, 0, + surfaceTransform, 0, + mSourceCoords_0, 0); + break; + case 90: + Matrix.multiplyMM(sourceCoords, 0, + surfaceTransform, 0, + mSourceCoords_90, 0); + break; + case 180: + Matrix.multiplyMM(sourceCoords, 0, + surfaceTransform, 0, + mSourceCoords_180, 0); + break; + case 270: + Matrix.multiplyMM(sourceCoords, 0, + surfaceTransform, 0, + mSourceCoords_270, 0); + break; + } + if (mLogVerbose) { + Log.v(TAG, "OrientationHint = " + mOrientation); + String temp = String.format("SetSourceRegion: %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f", + sourceCoords[4], sourceCoords[5],sourceCoords[0], sourceCoords[1], + sourceCoords[12], sourceCoords[13],sourceCoords[8], sourceCoords[9]); + Log.v(TAG, temp); + } + mFrameExtractor.setSourceRegion(sourceCoords[4], sourceCoords[5], + sourceCoords[0], sourceCoords[1], + sourceCoords[12], sourceCoords[13], + sourceCoords[8], sourceCoords[9]); + mOrientationUpdated = false; + } + + Frame output = context.getFrameManager().newFrame(mOutputFormat); + mFrameExtractor.process(mMediaFrame, output); + + long timestamp = mSurfaceTexture.getTimestamp(); + if (mLogVerbose) Log.v(TAG, "Timestamp: " + (timestamp / 1000000000.0) + " s"); + output.setTimestamp(timestamp); + + pushOutput("video", output); + output.release(); + + mPlaying = true; + } + + @Override + public void close(FilterContext context) { + if (mMediaPlayer.isPlaying()) { + mMediaPlayer.stop(); + } + mPrepared = false; + mGotSize = false; + mPlaying = false; + mPaused = false; + mCompleted = false; + mNewFrameAvailable = false; + + mMediaPlayer.release(); + mMediaPlayer = null; + mSurfaceTexture.release(); + mSurfaceTexture = null; + if (mLogVerbose) Log.v(TAG, "MediaSource closed"); + } + + @Override + public void tearDown(FilterContext context) { + if (mMediaFrame != null) { + mMediaFrame.release(); + } + } + + // When updating the port values of the filter, users can update sourceIsUrl to switch + // between using URL objects or Assets. + // If updating only sourceUrl/sourceAsset, MediaPlayer gets reset if the current player + // uses Url objects/Asset. + // Otherwise the new sourceUrl/sourceAsset is stored and will be used when users switch + // sourceIsUrl next time. + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Parameter update"); + if (name.equals("sourceUrl")) { + if (isOpen()) { + if (mLogVerbose) Log.v(TAG, "Opening new source URL"); + if (mSelectedIsUrl) { + setupMediaPlayer(mSelectedIsUrl); + } + } + } else if (name.equals("sourceAsset") ) { + if (isOpen()) { + if (mLogVerbose) Log.v(TAG, "Opening new source FD"); + if (!mSelectedIsUrl) { + setupMediaPlayer(mSelectedIsUrl); + } + } + } else if (name.equals("loop")) { + if (isOpen()) { + mMediaPlayer.setLooping(mLooping); + } + } else if (name.equals("sourceIsUrl")) { + if (isOpen()){ + if (mSelectedIsUrl){ + if (mLogVerbose) Log.v(TAG, "Opening new source URL"); + } else { + if (mLogVerbose) Log.v(TAG, "Opening new source Asset"); + } + setupMediaPlayer(mSelectedIsUrl); + } + } else if (name.equals("volume")) { + if (isOpen()) { + mMediaPlayer.setVolume(mVolume, mVolume); + } + } else if (name.equals("orientation") && mGotSize) { + if (mOrientation == 0 || mOrientation == 180) { + mOutputFormat.setDimensions(mWidth, mHeight); + } else { + mOutputFormat.setDimensions(mHeight, mWidth); + } + mOrientationUpdated = true; + } + } + + synchronized public void pauseVideo(boolean pauseState) { + if (isOpen()) { + if (pauseState && !mPaused) { + mMediaPlayer.pause(); + } else if (!pauseState && mPaused) { + mMediaPlayer.start(); + } + } + mPaused = pauseState; + } + + /** Creates a media player, sets it up, and calls prepare */ + synchronized private boolean setupMediaPlayer(boolean useUrl) { + mPrepared = false; + mGotSize = false; + mPlaying = false; + mPaused = false; + mCompleted = false; + mNewFrameAvailable = false; + + if (mLogVerbose) Log.v(TAG, "Setting up playback."); + + if (mMediaPlayer != null) { + // Clean up existing media players + if (mLogVerbose) Log.v(TAG, "Resetting existing MediaPlayer."); + mMediaPlayer.reset(); + } else { + // Create new media player + if (mLogVerbose) Log.v(TAG, "Creating new MediaPlayer."); + mMediaPlayer = new MediaPlayer(); + } + + if (mMediaPlayer == null) { + throw new RuntimeException("Unable to create a MediaPlayer!"); + } + + // Set up data sources, etc + try { + if (useUrl) { + if (mLogVerbose) Log.v(TAG, "Setting MediaPlayer source to URI " + mSourceUrl); + mMediaPlayer.setDataSource(mSourceUrl); + } else { + if (mLogVerbose) Log.v(TAG, "Setting MediaPlayer source to asset " + mSourceAsset); + mMediaPlayer.setDataSource(mSourceAsset.getFileDescriptor(), mSourceAsset.getStartOffset(), mSourceAsset.getLength()); + } + } catch(IOException e) { + mMediaPlayer.release(); + mMediaPlayer = null; + if (useUrl) { + throw new RuntimeException(String.format("Unable to set MediaPlayer to URL %s!", mSourceUrl), e); + } else { + throw new RuntimeException(String.format("Unable to set MediaPlayer to asset %s!", mSourceAsset), e); + } + } catch(IllegalArgumentException e) { + mMediaPlayer.release(); + mMediaPlayer = null; + if (useUrl) { + throw new RuntimeException(String.format("Unable to set MediaPlayer to URL %s!", mSourceUrl), e); + } else { + throw new RuntimeException(String.format("Unable to set MediaPlayer to asset %s!", mSourceAsset), e); + } + } + + mMediaPlayer.setLooping(mLooping); + mMediaPlayer.setVolume(mVolume, mVolume); + + // Bind it to our media frame + Surface surface = new Surface(mSurfaceTexture); + mMediaPlayer.setSurface(surface); + surface.release(); + + // Connect Media Player to callbacks + + mMediaPlayer.setOnVideoSizeChangedListener(onVideoSizeChangedListener); + mMediaPlayer.setOnPreparedListener(onPreparedListener); + mMediaPlayer.setOnCompletionListener(onCompletionListener); + + // Connect SurfaceTexture to callback + mSurfaceTexture.setOnFrameAvailableListener(onMediaFrameAvailableListener); + + if (mLogVerbose) Log.v(TAG, "Preparing MediaPlayer."); + mMediaPlayer.prepareAsync(); + + return true; + } + + private MediaPlayer.OnVideoSizeChangedListener onVideoSizeChangedListener = + new MediaPlayer.OnVideoSizeChangedListener() { + public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { + if (mLogVerbose) Log.v(TAG, "MediaPlayer sent dimensions: " + width + " x " + height); + if (!mGotSize) { + if (mOrientation == 0 || mOrientation == 180) { + mOutputFormat.setDimensions(width, height); + } else { + mOutputFormat.setDimensions(height, width); + } + mWidth = width; + mHeight = height; + } else { + if (mOutputFormat.getWidth() != width || + mOutputFormat.getHeight() != height) { + Log.e(TAG, "Multiple video size change events received!"); + } + } + synchronized(MediaSource.this) { + mGotSize = true; + MediaSource.this.notify(); + } + } + }; + + private MediaPlayer.OnPreparedListener onPreparedListener = + new MediaPlayer.OnPreparedListener() { + public void onPrepared(MediaPlayer mp) { + if (mLogVerbose) Log.v(TAG, "MediaPlayer is prepared"); + synchronized(MediaSource.this) { + mPrepared = true; + MediaSource.this.notify(); + } + } + }; + + private MediaPlayer.OnCompletionListener onCompletionListener = + new MediaPlayer.OnCompletionListener() { + public void onCompletion(MediaPlayer mp) { + if (mLogVerbose) Log.v(TAG, "MediaPlayer has completed playback"); + synchronized(MediaSource.this) { + mCompleted = true; + } + } + }; + + private SurfaceTexture.OnFrameAvailableListener onMediaFrameAvailableListener = + new SurfaceTexture.OnFrameAvailableListener() { + public void onFrameAvailable(SurfaceTexture surfaceTexture) { + if (mLogVerbose) Log.v(TAG, "New frame from media player"); + synchronized(MediaSource.this) { + if (mLogVerbose) Log.v(TAG, "New frame: notify"); + mNewFrameAvailable = true; + MediaSource.this.notify(); + if (mLogVerbose) Log.v(TAG, "New frame: notify done"); + } + } + }; + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureSource.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureSource.java new file mode 100644 index 0000000..37fa242 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureSource.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.filterpacks.videosrc; + +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.GLFrame; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.graphics.SurfaceTexture; +import android.media.MediaPlayer; +import android.os.ConditionVariable; +import android.opengl.Matrix; + +import java.io.IOException; +import java.io.FileDescriptor; +import java.lang.IllegalArgumentException; +import java.util.List; +import java.util.Set; + +import android.util.Log; + +/** <p>A filter that converts textures from a SurfaceTexture object into frames for + * processing in the filter framework.</p> + * + * <p>To use, connect up the sourceListener callback, and then when executing + * the graph, use the SurfaceTexture object passed to the callback to feed + * frames into the filter graph. For example, pass the SurfaceTexture into + * {#link + * android.hardware.Camera.setPreviewTexture(android.graphics.SurfaceTexture)}. + * This filter is intended for applications that need for flexibility than the + * CameraSource and MediaSource provide. Note that the application needs to + * provide width and height information for the SurfaceTextureSource, which it + * should obtain from wherever the SurfaceTexture data is coming from to avoid + * unnecessary resampling.</p> + * + * @hide + */ +public class SurfaceTextureSource extends Filter { + + /** User-visible parameters */ + + /** The callback interface for the sourceListener parameter */ + public interface SurfaceTextureSourceListener { + public void onSurfaceTextureSourceReady(SurfaceTexture source); + } + /** A callback to send the internal SurfaceTexture object to, once it is + * created. This callback will be called when the the filter graph is + * preparing to execute, but before any processing has actually taken + * place. The SurfaceTexture object passed to this callback is the only way + * to feed this filter. When the filter graph is shutting down, this + * callback will be called again with null as the source. + * + * This callback may be called from an arbitrary thread, so it should not + * assume it is running in the UI thread in particular. + */ + @GenerateFinalPort(name = "sourceListener") + private SurfaceTextureSourceListener mSourceListener; + + /** The width of the output image frame. If the texture width for the + * SurfaceTexture source is known, use it here to minimize resampling. */ + @GenerateFieldPort(name = "width") + private int mWidth; + + /** The height of the output image frame. If the texture height for the + * SurfaceTexture source is known, use it here to minimize resampling. */ + @GenerateFieldPort(name = "height") + private int mHeight; + + /** Whether the filter will always wait for a new frame from its + * SurfaceTexture, or whether it will output an old frame again if a new + * frame isn't available. The filter will always wait for the first frame, + * to avoid outputting a blank frame. Defaults to true. + */ + @GenerateFieldPort(name = "waitForNewFrame", hasDefault = true) + private boolean mWaitForNewFrame = true; + + /** Maximum timeout before signaling error when waiting for a new frame. Set + * this to zero to disable the timeout and wait indefinitely. In milliseconds. + */ + @GenerateFieldPort(name = "waitTimeout", hasDefault = true) + private int mWaitTimeout = 1000; + + /** Whether a timeout is an exception-causing failure, or just causes the + * filter to close. + */ + @GenerateFieldPort(name = "closeOnTimeout", hasDefault = true) + private boolean mCloseOnTimeout = false; + + // Variables for input->output conversion + private GLFrame mMediaFrame; + private ShaderProgram mFrameExtractor; + private SurfaceTexture mSurfaceTexture; + private MutableFrameFormat mOutputFormat; + private ConditionVariable mNewFrameAvailable; + private boolean mFirstFrame; + + private float[] mFrameTransform; + private float[] mMappedCoords; + // These default source coordinates perform the necessary flip + // for converting from MFF/Bitmap origin to OpenGL origin. + private static final float[] mSourceCoords = { 0, 1, 0, 1, + 1, 1, 0, 1, + 0, 0, 0, 1, + 1, 0, 0, 1 }; + // Shader for output + private final String mRenderShader = + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "uniform samplerExternalOES tex_sampler_0;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(tex_sampler_0, v_texcoord);\n" + + "}\n"; + + // Variables for logging + + private static final String TAG = "SurfaceTextureSource"; + private static final boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + + public SurfaceTextureSource(String name) { + super(name); + mNewFrameAvailable = new ConditionVariable(); + mFrameTransform = new float[16]; + mMappedCoords = new float[16]; + } + + @Override + public void setupPorts() { + // Add input port + addOutputPort("video", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + } + + private void createFormats() { + mOutputFormat = ImageFormat.create(mWidth, mHeight, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + } + + @Override + protected void prepare(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Preparing SurfaceTextureSource"); + + createFormats(); + + // Prepare input + mMediaFrame = (GLFrame)context.getFrameManager().newBoundFrame(mOutputFormat, + GLFrame.EXTERNAL_TEXTURE, + 0); + + // Prepare output + mFrameExtractor = new ShaderProgram(context, mRenderShader); + } + + @Override + public void open(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Opening SurfaceTextureSource"); + // Create SurfaceTexture anew each time - it can use substantial memory. + mSurfaceTexture = new SurfaceTexture(mMediaFrame.getTextureId()); + // Connect SurfaceTexture to callback + mSurfaceTexture.setOnFrameAvailableListener(onFrameAvailableListener); + // Connect SurfaceTexture to source + mSourceListener.onSurfaceTextureSourceReady(mSurfaceTexture); + mFirstFrame = true; + } + + @Override + public void process(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Processing new frame"); + + // First, get new frame if available + if (mWaitForNewFrame || mFirstFrame) { + boolean gotNewFrame; + if (mWaitTimeout != 0) { + gotNewFrame = mNewFrameAvailable.block(mWaitTimeout); + if (!gotNewFrame) { + if (!mCloseOnTimeout) { + throw new RuntimeException("Timeout waiting for new frame"); + } else { + if (mLogVerbose) Log.v(TAG, "Timeout waiting for a new frame. Closing."); + closeOutputPort("video"); + return; + } + } + } else { + mNewFrameAvailable.block(); + } + mNewFrameAvailable.close(); + mFirstFrame = false; + } + + mSurfaceTexture.updateTexImage(); + + mSurfaceTexture.getTransformMatrix(mFrameTransform); + Matrix.multiplyMM(mMappedCoords, 0, + mFrameTransform, 0, + mSourceCoords, 0); + mFrameExtractor.setSourceRegion(mMappedCoords[0], mMappedCoords[1], + mMappedCoords[4], mMappedCoords[5], + mMappedCoords[8], mMappedCoords[9], + mMappedCoords[12], mMappedCoords[13]); + // Next, render to output + Frame output = context.getFrameManager().newFrame(mOutputFormat); + mFrameExtractor.process(mMediaFrame, output); + + output.setTimestamp(mSurfaceTexture.getTimestamp()); + + pushOutput("video", output); + output.release(); + } + + @Override + public void close(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "SurfaceTextureSource closed"); + mSourceListener.onSurfaceTextureSourceReady(null); + mSurfaceTexture.release(); + mSurfaceTexture = null; + } + + @Override + public void tearDown(FilterContext context) { + if (mMediaFrame != null) { + mMediaFrame.release(); + } + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (name.equals("width") || name.equals("height") ) { + mOutputFormat.setDimensions(mWidth, mHeight); + } + } + + private SurfaceTexture.OnFrameAvailableListener onFrameAvailableListener = + new SurfaceTexture.OnFrameAvailableListener() { + public void onFrameAvailable(SurfaceTexture surfaceTexture) { + if (mLogVerbose) Log.v(TAG, "New frame from SurfaceTexture"); + mNewFrameAvailable.open(); + } + }; +} diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java new file mode 100644 index 0000000..436caab --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package android.filterpacks.videosrc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.FilterSurfaceView; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.GLEnvironment; +import android.filterfw.core.GLFrame; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.filterfw.geometry.Quad; +import android.filterfw.geometry.Point; + +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import android.graphics.Rect; +import android.graphics.SurfaceTexture; + +import android.util.Log; + +/** + * @hide + */ +public class SurfaceTextureTarget extends Filter { + + private final int RENDERMODE_STRETCH = 0; + private final int RENDERMODE_FIT = 1; + private final int RENDERMODE_FILL_CROP = 2; + private final int RENDERMODE_CUSTOMIZE = 3; + + /** Required. Sets the destination surfaceTexture. + */ + @GenerateFinalPort(name = "surfaceTexture") + private SurfaceTexture mSurfaceTexture; + + /** Required. Sets the width of the output surfaceTexture images */ + @GenerateFinalPort(name = "width") + private int mScreenWidth; + + /** Required. Sets the height of the output surfaceTexture images */ + @GenerateFinalPort(name = "height") + private int mScreenHeight; + + + /** Optional. Control how the incoming frames are rendered onto the + * output. Default is FIT. + * RENDERMODE_STRETCH: Just fill the output surfaceView. + * RENDERMODE_FIT: Keep aspect ratio and fit without cropping. May + * have black bars. + * RENDERMODE_FILL_CROP: Keep aspect ratio and fit without black + * bars. May crop. + */ + @GenerateFieldPort(name = "renderMode", hasDefault = true) + private String mRenderModeString; + + @GenerateFieldPort(name = "sourceQuad", hasDefault = true) + private Quad mSourceQuad = new Quad(new Point(0.0f, 1.0f), + new Point(1.0f, 1.0f), + new Point(0.0f, 0.0f), + new Point(1.0f, 0.0f)); + + @GenerateFieldPort(name = "targetQuad", hasDefault = true) + private Quad mTargetQuad = new Quad(new Point(0.0f, 0.0f), + new Point(1.0f, 0.0f), + new Point(0.0f, 1.0f), + new Point(1.0f, 1.0f)); + + private int mSurfaceId; + + private ShaderProgram mProgram; + private GLFrame mScreen; + private int mRenderMode = RENDERMODE_FIT; + private float mAspectRatio = 1.f; + + private boolean mLogVerbose; + private static final String TAG = "SurfaceTextureTarget"; + + public SurfaceTextureTarget(String name) { + super(name); + + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + @Override + public void setupPorts() { + // Make sure we have a SurfaceView + if (mSurfaceTexture == null) { + throw new RuntimeException("Null SurfaceTexture passed to SurfaceTextureTarget"); + } + + // Add input port - will accept anything that's 4-channel. + addMaskedInputPort("frame", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + } + + public void updateRenderMode() { + if (mRenderModeString != null) { + if (mRenderModeString.equals("stretch")) { + mRenderMode = RENDERMODE_STRETCH; + } else if (mRenderModeString.equals("fit")) { + mRenderMode = RENDERMODE_FIT; + } else if (mRenderModeString.equals("fill_crop")) { + mRenderMode = RENDERMODE_FILL_CROP; + } else if (mRenderModeString.equals("customize")) { + mRenderMode = RENDERMODE_CUSTOMIZE; + } else { + throw new RuntimeException("Unknown render mode '" + mRenderModeString + "'!"); + } + } + updateTargetRect(); + } + + @Override + public void prepare(FilterContext context) { + // Create identity shader to render, and make sure to render upside-down, as textures + // are stored internally bottom-to-top. + mProgram = ShaderProgram.createIdentity(context); + mProgram.setSourceRect(0, 1, 1, -1); + mProgram.setClearColor(0.0f, 0.0f, 0.0f); + + updateRenderMode(); + + // Create a frame representing the screen + MutableFrameFormat screenFormat = new MutableFrameFormat(FrameFormat.TYPE_BYTE, + FrameFormat.TARGET_GPU); + screenFormat.setBytesPerSample(4); + screenFormat.setDimensions(mScreenWidth, mScreenHeight); + mScreen = (GLFrame)context.getFrameManager().newBoundFrame(screenFormat, + GLFrame.EXISTING_FBO_BINDING, + 0); + } + + @Override + public void open(FilterContext context) { + // Set up SurfaceTexture internals + mSurfaceId = context.getGLEnvironment().registerSurfaceTexture(mSurfaceTexture, mScreenWidth, mScreenHeight); + } + + @Override + public void process(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Starting frame processing"); + + GLEnvironment glEnv = context.getGLEnvironment(); + + // Get input frame + Frame input = pullInput("frame"); + boolean createdFrame = false; + + float currentAspectRatio = (float)input.getFormat().getWidth() / input.getFormat().getHeight(); + if (currentAspectRatio != mAspectRatio) { + if (mLogVerbose) Log.v(TAG, "New aspect ratio: " + currentAspectRatio +", previously: " + mAspectRatio); + mAspectRatio = currentAspectRatio; + updateTargetRect(); + } + + // See if we need to copy to GPU + Frame gpuFrame = null; + if (mLogVerbose) Log.v("SurfaceTextureTarget", "Got input format: " + input.getFormat()); + + int target = input.getFormat().getTarget(); + if (target != FrameFormat.TARGET_GPU) { + gpuFrame = context.getFrameManager().duplicateFrameToTarget(input, + FrameFormat.TARGET_GPU); + createdFrame = true; + } else { + gpuFrame = input; + } + + // Activate our surface + glEnv.activateSurfaceWithId(mSurfaceId); + + // Process + mProgram.process(gpuFrame, mScreen); + + glEnv.setSurfaceTimestamp(input.getTimestamp()); + + // And swap buffers + glEnv.swapBuffers(); + + if (createdFrame) { + gpuFrame.release(); + } + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + updateRenderMode(); + } + + @Override + public void tearDown(FilterContext context) { + if (mScreen != null) { + mScreen.release(); + } + } + + private void updateTargetRect() { + if (mScreenWidth > 0 && mScreenHeight > 0 && mProgram != null) { + float screenAspectRatio = (float)mScreenWidth / mScreenHeight; + float relativeAspectRatio = screenAspectRatio / mAspectRatio; + + if (relativeAspectRatio == 1.0f && mRenderMode != RENDERMODE_CUSTOMIZE) { + mProgram.setClearsOutput(false); + } else { + switch (mRenderMode) { + case RENDERMODE_STRETCH: + mProgram.setTargetRect(0, 0, 1, 1); + mTargetQuad.p0.set(0f, 0.0f); + mTargetQuad.p1.set(1f, 0.0f); + mTargetQuad.p2.set(0f, 1.0f); + mTargetQuad.p3.set(1f, 1.0f); + mProgram.setClearsOutput(false); + break; + case RENDERMODE_FIT: + if (relativeAspectRatio > 1.0f) { + // Screen is wider than the camera, scale down X + mTargetQuad.p0.set(0.5f - 0.5f / relativeAspectRatio, 0.0f); + mTargetQuad.p1.set(0.5f + 0.5f / relativeAspectRatio, 0.0f); + mTargetQuad.p2.set(0.5f - 0.5f / relativeAspectRatio, 1.0f); + mTargetQuad.p3.set(0.5f + 0.5f / relativeAspectRatio, 1.0f); + + } else { + // Screen is taller than the camera, scale down Y + mTargetQuad.p0.set(0.0f, 0.5f - 0.5f * relativeAspectRatio); + mTargetQuad.p1.set(1.0f, 0.5f - 0.5f * relativeAspectRatio); + mTargetQuad.p2.set(0.0f, 0.5f + 0.5f * relativeAspectRatio); + mTargetQuad.p3.set(1.0f, 0.5f + 0.5f * relativeAspectRatio); + } + mProgram.setClearsOutput(true); + break; + case RENDERMODE_FILL_CROP: + if (relativeAspectRatio > 1) { + // Screen is wider than the camera, crop in Y + mTargetQuad.p0.set(0.0f, 0.5f - 0.5f * relativeAspectRatio); + mTargetQuad.p1.set(1.0f, 0.5f - 0.5f * relativeAspectRatio); + mTargetQuad.p2.set(0.0f, 0.5f + 0.5f * relativeAspectRatio); + mTargetQuad.p3.set(1.0f, 0.5f + 0.5f * relativeAspectRatio); + } else { + // Screen is taller than the camera, crop in X + mTargetQuad.p0.set(0.5f - 0.5f / relativeAspectRatio, 0.0f); + mTargetQuad.p1.set(0.5f + 0.5f / relativeAspectRatio, 0.0f); + mTargetQuad.p2.set(0.5f - 0.5f / relativeAspectRatio, 1.0f); + mTargetQuad.p3.set(0.5f + 0.5f / relativeAspectRatio, 1.0f); + } + mProgram.setClearsOutput(true); + break; + case RENDERMODE_CUSTOMIZE: + ((ShaderProgram) mProgram).setSourceRegion(mSourceQuad); + break; + } + ((ShaderProgram) mProgram).setTargetRegion(mTargetQuad); + } + } + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/package-info.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/package-info.java new file mode 100644 index 0000000..d8fd0bd --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/package-info.java @@ -0,0 +1,4 @@ +/** + * @hide + */ +package android.filterpacks.videosrc; diff --git a/media/mca/filterpacks/native/base/geometry.cpp b/media/mca/filterpacks/native/base/geometry.cpp new file mode 100644 index 0000000..7812d50 --- /dev/null +++ b/media/mca/filterpacks/native/base/geometry.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cutils/log.h> +#include <cmath> + +#include "geometry.h" + +namespace android { +namespace filterfw { + +float Point::Length() const { + return std::sqrt(x_ * x_ + y_ * y_); +} + +bool Point::ScaleTo(float new_length) { + float length = Length(); + if (length == 0.0f) { + return false; + } + x_ *= new_length / length; + y_ *= new_length / length; + return true; +} + +float Point::Distance(const Point& p0, const Point& p1) { + Point diff = p1 - p0; + return diff.Length(); +} + +Point Point::operator+(const Point& other) const { + Point out; + out.x_ = x_ + other.x_; + out.y_ = y_ + other.y_; + return out; +} + +Point Point::operator-(const Point& other) const { + Point out; + out.x_ = x_ - other.x_; + out.y_ = y_ - other.y_; + return out; +} + +Point Point::operator*(float factor) const { + Point out; + out.x_ = factor * x_; + out.y_ = factor * y_; + return out; +} + +void Point::Rotate90Clockwise() { + const float x = x_; + x_ = y_; + y_ = -x; +} + +bool Rect::ExpandToAspectRatio(float ratio) { + if (width <= 0.0f || height <= 0.0f || ratio <= 0.0f) { + return false; + } + + const float current_ratio = width / height; + if (current_ratio < ratio) { + const float dx = width * (ratio / current_ratio - 1.0f); + x -= dx / 2.0f; + width += dx; + } else { + const float dy = height * (current_ratio / ratio - 1.0f); + y -= dy / 2.0f; + height += dy; + } + return true; +} + +bool Rect::ExpandToMinLength(float length) { + if (width <= 0.0f || height <= 0.0f || length <= 0.0f) { + return false; + } + + const float current_length = width > height ? width : height; + if (length > current_length) { + const float dx = width * (length / current_length - 1.0f); + x -= dx / 2.0f; + width += dx; + const float dy = height * (length / current_length - 1.0f); + y -= dy / 2.0f; + height += dy; + } + return true; +} + +bool Rect::ScaleWithLengthLimit(float factor, float max_length) { + if (width <= 0.0f || height <= 0.0f || factor <= 0.0f) { + return false; + } + + const float current_length = width > height ? width : height; + if (current_length >= max_length) { + return true; + } + + float f = factor; + if (current_length * f > max_length) { + f *= max_length / (current_length * f); + } + + const float dx = width * (f - 1.0f); + x -= dx / 2.0f; + width += dx; + const float dy = height * (f - 1.0f); + y -= dy / 2.0f; + height += dy; + return true; +} + +const Point& Quad::point(int ix) const { + ALOG_ASSERT(ix < static_cast<int>(points_.size()), "Access out of bounds"); + return points_[ix]; +} + +bool SlantedRect::FromCenterAxisAndLengths(const Point& center, + const Point& vert_axis, + const Point& lengths) { + Point dy = vert_axis; + if (!dy.ScaleTo(lengths.y() / 2.0f)) { + ALOGE("Illegal axis: %f %f", vert_axis.x(), vert_axis.y()); + return false; + } + + Point dx = dy; + dx.Rotate90Clockwise(); + dx.ScaleTo(lengths.x() / 2.0f); + + points_[0] = center - dx - dy; + points_[1] = center + dx - dy; + points_[2] = center - dx + dy; + points_[3] = center + dx + dy; + + width_ = lengths.x(); + height_ = lengths.y(); + + return true; +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterpacks/native/base/geometry.h b/media/mca/filterpacks/native/base/geometry.h new file mode 100644 index 0000000..40a9343 --- /dev/null +++ b/media/mca/filterpacks/native/base/geometry.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_FILTERFW_FILTERPACKS_BASE_GEOMETRY_H +#define ANDROID_FILTERFW_FILTERPACKS_BASE_GEOMETRY_H + +#include <vector> + +namespace android { +namespace filterfw { + +// This is an initial implementation of some geometrical structures. This is +// likely to grow and become more sophisticated in the future. + +class Point { + public: + Point() : x_(0.0f), y_(0.0f) {} + Point(float x, float y) : x_(x), y_(y) {} + + float x() const { return x_; } + float y() const { return y_; } + + float Length() const; + bool ScaleTo(float new_length); + static float Distance(const Point& p0, const Point& p1); + + // Add more of these as needed: + Point operator+(const Point& other) const; + Point operator-(const Point& other) const; + Point operator*(float factor) const; + + void Rotate90Clockwise(); + + private: + float x_, y_; +}; + +class Quad { + public: + Quad() : points_(4) {} + virtual ~Quad() {} + + Quad(const Point& p0, const Point& p1, const Point& p2, const Point& p3) + : points_(4) { + points_[0] = p0; + points_[1] = p1; + points_[2] = p2; + points_[3] = p3; + } + + const std::vector<Point>& points() const { return points_; } + const Point& point(int ix) const; + + protected: + std::vector<Point> points_; +}; + +class SlantedRect : public Quad { + public: + SlantedRect() : width_(0.0f), height_(0.0f) {} + virtual ~SlantedRect() {} + + bool FromCenterAxisAndLengths(const Point& center, + const Point& vert_axis, + const Point& lenghts); + + float width() const { return width_; } + float height() const { return height_; } + + private: + float width_; + float height_; +}; + +struct Rect { + float x, y, width, height; + + Rect() { + x = y = 0.0f; + width = height = 1.0f; + } + + Rect(float x, float y, float width, float height) { + this->x = x; + this->y = y; + this->width = width; + this->height = height; + } + + bool ExpandToAspectRatio(float ratio); + bool ExpandToMinLength(float length); + bool ScaleWithLengthLimit(float factor, float max_length); +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_FILTERPACKS_BASE_GEOMETRY_H diff --git a/media/mca/filterpacks/native/base/time_util.cpp b/media/mca/filterpacks/native/base/time_util.cpp new file mode 100644 index 0000000..1a78a95 --- /dev/null +++ b/media/mca/filterpacks/native/base/time_util.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "time_util.h" +#include "utilities.h" + +#include <cutils/log.h> +#include <sys/time.h> +#include <map> + +namespace android { +namespace filterfw { + +uint64_t getTimeUs() { + static long basesec; + struct timeval tv; + uint64_t nowtime; + gettimeofday(&tv, 0); + if (basesec == 0) { + basesec = tv.tv_sec; + } + nowtime = (uint64_t)(tv.tv_sec - basesec) * (uint64_t)1000000 + + (uint64_t)tv.tv_usec; + return nowtime; +} + +const uint64_t NamedStopWatch::kDefaultLoggingPeriodInFrames = 100; + +NamedStopWatch::NamedStopWatch(const std::string& name) + : mName(name), + mLoggingPeriodInFrames(kDefaultLoggingPeriodInFrames), + mStartUSec(0), + mNumCalls(0), + mTotalUSec(0) { +} + +void NamedStopWatch::Start() { + mStartUSec = getTimeUs(); +} + +void NamedStopWatch::Stop() { + if (!mStartUSec) { + return; + } + uint64_t stopUSec = getTimeUs(); + if (stopUSec > mStartUSec) { + ++mNumCalls; + mTotalUSec += stopUSec - mStartUSec; + if (mNumCalls % mLoggingPeriodInFrames == 0) { + const float mSec = TotalUSec() * 1.0E-3f / NumCalls(); + ALOGE("%s: %f ms", Name().c_str(), mSec); + } + } + mStartUSec = 0; +} + +namespace { +static NamedStopWatch* GetWatchForName(const std::string& watch_name) { + // TODO: this leaks the NamedStopWatch objects. Replace it with a + // singleton to avoid that and make it thread safe. + static std::map<std::string, NamedStopWatch*> watches; + NamedStopWatch* watch = FindPtrOrNull(watches, watch_name); + if (!watch) { + watch = new NamedStopWatch(watch_name); + watches[watch_name] = watch; + } + return watch; +}; +} // namespace + +ScopedTimer::ScopedTimer(const std::string& stop_watch_name) { + mWatch = GetWatchForName(stop_watch_name); + mWatch->Start(); +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterpacks/native/base/time_util.h b/media/mca/filterpacks/native/base/time_util.h new file mode 100644 index 0000000..60d76c6 --- /dev/null +++ b/media/mca/filterpacks/native/base/time_util.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_FILTERFW_FILTERPACKS_BASE_TIME_UTIL_H +#define ANDROID_FILTERFW_FILTERPACKS_BASE_TIME_UTIL_H + +#include <string> +#include <utils/RefBase.h> + +#define LOG_MFF_RUNNING_TIMES 0 + +namespace android { +namespace filterfw { + +uint64_t getTimeUs(); + +class NamedStopWatch : public RefBase { + public: + static const uint64_t kDefaultLoggingPeriodInFrames; + + explicit NamedStopWatch(const std::string& name); + void Start(); + void Stop(); + + void SetName(const std::string& name) { mName = name; } + void SetLoggingPeriodInFrames(uint64_t numFrames) { + mLoggingPeriodInFrames = numFrames; + } + + const std::string& Name() const { return mName; } + uint64_t NumCalls() const { return mNumCalls; } + uint64_t TotalUSec() const { return mTotalUSec; } + + private: + std::string mName; + uint64_t mLoggingPeriodInFrames; + uint64_t mStartUSec; + uint64_t mNumCalls; + uint64_t mTotalUSec; +}; + +class ScopedTimer { + public: + explicit ScopedTimer(const std::string& stop_watch_name); + explicit ScopedTimer(NamedStopWatch* watch) + : mWatch(watch) { mWatch->Start(); } + ~ScopedTimer() { mWatch->Stop(); } + + private: + NamedStopWatch* mWatch; +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_FILTERPACKS_BASE_TIME_UTIL_H diff --git a/media/mca/filterpacks/native/base/utilities.h b/media/mca/filterpacks/native/base/utilities.h new file mode 100644 index 0000000..302e177 --- /dev/null +++ b/media/mca/filterpacks/native/base/utilities.h @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_FILTERFW_FILTERPACKS_BASE_UTILITIES_H +#define ANDROID_FILTERFW_FILTERPACKS_BASE_UTILITIES_H + +#include <set> +#include <utility> + +namespace android { +namespace filterfw { + +// Convenience Macro to make copy constructor and assignment operator private +// (thereby disallowing copying and assigning). +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + +// A macro to disallow all the implicit constructors, namely the +// default constructor, copy constructor and operator= functions. +#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName(); \ + DISALLOW_COPY_AND_ASSIGN(TypeName) + +// STLDeleteContainerPointers() +// For a range within a container of pointers, calls delete +// (non-array version) on these pointers. +// NOTE: for these three functions, we could just implement a DeleteObject +// functor and then call for_each() on the range and functor, but this +// requires us to pull in all of algorithm.h, which seems expensive. +// For hash_[multi]set, it is important that this deletes behind the iterator +// because the hash_set may call the hash function on the iterator when it is +// advanced, which could result in the hash function trying to deference a +// stale pointer. +template <class ForwardIterator> +void STLDeleteContainerPointers(ForwardIterator begin, + ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete *temp; + } +} + +// Given an STL container consisting of (key, value) pairs, STLDeleteValues +// deletes all the "value" components and clears the container. Does nothing +// in the case it's given a NULL pointer. +template <class T> +void STLDeleteValues(T *v) { + if (!v) return; + for (typename T::iterator i = v->begin(); i != v->end(); ++i) { + delete i->second; + } + v->clear(); +} + +// Perform a lookup in a map or hash_map. +// If the key is present a const pointer to the associated value is returned, +// otherwise a NULL pointer is returned. +template <class Collection> +const typename Collection::value_type::second_type* +FindOrNull(const Collection& collection, + const typename Collection::value_type::first_type& key) { + typename Collection::const_iterator it = collection.find(key); + if (it == collection.end()) { + return 0; + } + return &it->second; +} + +// A simple class that gives checklist functionality: There are essemtially two +// operations defined on a CheckList: +// - Adding a new (unchecked) item. +// - Checking off an item. +// When checking off the last remaining item CheckItem() returns true. +template<typename T> +class CheckList { + public: + // Add a new unchecked item. Does nothing if item is already in checklist. + void AddItem(const T& item); + + // Check off an item in the checklist. Returns true if all items have been + // checked. + bool CheckItem(const T& item); + + // Clear the checklist. + void Clear() { + items_.clear(); + } + + private: + std::set<T> items_; +}; + +template<typename T> +void CheckList<T>::AddItem(const T& item) { + if (!ContainsKey(items_, item)) + items_.insert(item); +} + +template<typename T> +bool CheckList<T>::CheckItem(const T& item) { + typename std::set<T>::iterator iter = items_.find(item); + if (iter != items_.end()) + items_.erase(iter); + return items_.empty(); +} + +// Perform a lookup in a map or hash_map whose values are pointers. +// If the key is present a const pointer to the associated value is returned, +// otherwise a NULL pointer is returned. +// This function does not distinguish between a missing key and a key mapped +// to a NULL value. +template <class Collection> +const typename Collection::value_type::second_type +FindPtrOrNull(const Collection& collection, + const typename Collection::value_type::first_type& key) { + typename Collection::const_iterator it = collection.find(key); + if (it == collection.end()) { + return 0; + } + return it->second; +} + +// Test to see if a set, map, hash_set or hash_map contains a particular key. +// Returns true if the key is in the collection. +template <typename Collection, typename Key> +bool ContainsKey(const Collection& collection, const Key& key) { + return collection.find(key) != collection.end(); +} + +// Insert a new key and value into a map or hash_map. +// If the key is not present in the map the key and value are +// inserted, otherwise nothing happens. True indicates that an insert +// took place, false indicates the key was already present. +template <class Collection, class Key, class Value> +bool InsertIfNotPresent(Collection * const collection, + const Key& key, const Value& value) { + std::pair<typename Collection::iterator, bool> ret = + collection->insert(typename Collection::value_type(key, value)); + return ret.second; +} + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_FILTERPACKS_BASE_UTILITIES_H diff --git a/media/mca/filterpacks/native/base/vec_types.h b/media/mca/filterpacks/native/base/vec_types.h new file mode 100644 index 0000000..65967c9 --- /dev/null +++ b/media/mca/filterpacks/native/base/vec_types.h @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_FILTERFW_FILTERPACKS_BASE_VEC_TYPES_H +#define ANDROID_FILTERFW_FILTERPACKS_BASE_VEC_TYPES_H + +namespace android { +namespace filterfw { + +template < class T, int dim> +class VecBase { + public: + T data[dim]; + VecBase() {} + VecBase<T,dim>& operator = (const VecBase<T, dim> &x) { + memcpy(data, x.data, sizeof(T)*dim); + return *this; + } + T & operator [] (int i) { + // out of boundary not checked + return data[i]; + } + const T & operator [] (int i) const { + // out of boundary not checked + return data[i]; + } + T Length() { + double sum = 0; + for (int i = 0; i < dim; ++i) + sum += static_cast<double> (data[i] * data[i]); + return static_cast<T>(sqrt(sum)); + } +}; + +template < class T, int dim> +class Vec : public VecBase<T,dim> { + public: + Vec() {} + Vec<T,dim>& operator = (const Vec<T, dim> &x) { + memcpy(this->data, x.data, sizeof(T)*dim); + return *this; + } +}; + +template <class T, int dim> +Vec<T, dim> operator + (const Vec<T,dim> &x, const Vec<T,dim> &y) { + Vec<T, dim> out; + for (int i = 0; i < dim; i++) + out.data[i] = x.data[i] + y.data[i]; + return out; +} + +template <class T, int dim> +Vec<T, dim> operator - (const Vec<T,dim> &x, const Vec<T,dim> &y) { + Vec<T, dim> out; + for (int i = 0; i < dim; i++) + out.data[i] = x.data[i] - y.data[i]; + return out; +} + +template <class T, int dim> +Vec<T, dim> operator * (const Vec<T,dim> &x, const Vec<T,dim> &y) { + Vec<T, dim> out; + for (int i = 0; i < dim; i++) + out.data[i] = x.data[i] * y.data[i]; + return out; +} + +template <class T, int dim> +Vec<T, dim> operator / (const Vec<T,dim> &x, const Vec<T,dim> &y) { + Vec<T, dim> out; + for (int i = 0; i < dim; i++) + out.data[i] = x.data[i] / y.data[i]; + return out; +} + +template <class T, int dim> +T dot(const Vec<T,dim> &x, const Vec<T,dim> &y) { + T out = 0; + for (int i = 0; i < dim; i++) + out += x.data[i] * y.data[i]; + return out; +} + +template <class T, int dim> +Vec<T, dim> operator * (const Vec<T,dim> &x, T scale) { + Vec<T, dim> out; + for (int i = 0; i < dim; i++) + out.data[i] = x.data[i] * scale; + return out; +} + +template <class T, int dim> +Vec<T, dim> operator / (const Vec<T,dim> &x, T scale) { + Vec<T, dim> out; + for (int i = 0; i < dim; i++) + out.data[i] = x.data[i] / scale; + return out; +} + +template <class T, int dim> +Vec<T, dim> operator + (const Vec<T,dim> &x, T val) { + Vec<T, dim> out; + for (int i = 0; i < dim; i++) + out.data[i] = x.data[i] + val; + return out; +} + +// specialization for vec2, vec3, vec4 float +template<> +class Vec<float, 2> : public VecBase<float, 2> { +public: + Vec() {} + Vec(float x, float y) { + data[0] = x; + data[1] = y; + } + Vec<float, 2>& operator = (const Vec<float, 2> &x) { + memcpy(data, x.data, sizeof(float)*2); + return *this; + } +}; + +template<> +class Vec<float, 3> { +public: + float data[3]; + Vec() {} + Vec(float x, float y, float z) { + data[0] = x; + data[1] = y; + data[2] = z; + } + Vec<float, 3>& operator = (const Vec<float, 3> &x) { + memcpy(data, x.data, sizeof(float)*3); + return *this; + } +}; + +template<> +class Vec<float, 4> { +public: + float data[4]; + Vec() {} + Vec(float x, float y, float z, float w) { + data[0] = x; + data[1] = y; + data[2] = z; + data[3] = w; + } + Vec<float, 4>& operator = (const Vec<float, 4> &x) { + memcpy(data, x.data, sizeof(float)*4); + return *this; + } +}; + +typedef Vec<float,2> Vec2f; +typedef Vec<float,3> Vec3f; +typedef Vec<float,4> Vec4f; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_FILTERPACKS_BASE_VEC_TYPES_H diff --git a/media/mca/filterpacks/native/imageproc/brightness.c b/media/mca/filterpacks/native/imageproc/brightness.c new file mode 100644 index 0000000..f4addf1 --- /dev/null +++ b/media/mca/filterpacks/native/imageproc/brightness.c @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <android/log.h> +#include <stdlib.h> + +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "MCA", __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, "MCA", __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "MCA", __VA_ARGS__) + +typedef struct { + float brightness; +} BrightnessParameters; + +typedef union { + int value; + char rgba[4]; +} Pixel; + +void brightness_init(void** user_data) { + (*user_data) = malloc(sizeof(BrightnessParameters)); +} + +void brightness_teardown(void* user_data) { + free(user_data); +} + +void brightness_setvalue(const char* key, const char* value, void* user_data) { + if (strcmp(key, "brightness") == 0) + ((BrightnessParameters*)user_data)->brightness = atof(value); + else + LOGE("Unknown parameter: %s!", key); +} + +int brightness_process(const char** inputs, + const int* input_sizes, + int input_count, + char* output, + int output_size, + void* user_data) { + // Make sure we have exactly one input + if (input_count != 1) { + LOGE("Brightness: Incorrect input count! Expected 1 but got %d!", input_count); + return 0; + } + + // Make sure sizes match up + if (input_sizes[0] != output_size) { + LOGE("Brightness: Input-output sizes do not match up. %d vs. %d!", input_sizes[0], output_size); + return 0; + } + + // Get the input and output pointers + const int* input_ptr = (int*)inputs[0]; + int* output_ptr = (int*)output; + const int* end_ptr = input_ptr + (output_size / 4); + if (!input_ptr || !output_ptr) { + LOGE("Brightness: No input or output pointer found!"); + return 0; + } + + // Get the parameters + BrightnessParameters* params = (BrightnessParameters*)user_data; + const float brightness = params->brightness; + + // Run the brightness adjustment + const int factor = (int)(brightness * 255.0f); + Pixel pixel; + while (input_ptr < end_ptr) { + pixel.value = *(input_ptr++); + + const short r = (pixel.rgba[0] * factor) / 255; + const short g = (pixel.rgba[1] * factor) / 255; + const short b = (pixel.rgba[2] * factor) / 255; + + *(output_ptr++) = (r > 255 ? 255 : r) + | ((g > 255 ? 255 : g) << 8) + | ((b > 255 ? 255 : b) << 16) + | (pixel.rgba[3] << 24); + } + + return 1; +} + diff --git a/media/mca/filterpacks/native/imageproc/contrast.c b/media/mca/filterpacks/native/imageproc/contrast.c new file mode 100644 index 0000000..ea8c8d2 --- /dev/null +++ b/media/mca/filterpacks/native/imageproc/contrast.c @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <android/log.h> +#include <stdlib.h> + +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "MCA", __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, "MCA", __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "MCA", __VA_ARGS__) + +typedef struct { + float contrast; +} ContrastParameters; + +void contrast_init(void** user_data) { + (*user_data) = malloc(sizeof(ContrastParameters)); +} + +void contrast_teardown(void* user_data) { + free(user_data); +} + +void contrast_setvalue(const char* key, const char* value, void* user_data) { + if (strcmp(key, "contrast") == 0) + ((ContrastParameters*)user_data)->contrast = atof(value); + else + LOGE("Unknown parameter: %s!", key); +} + +int contrast_process(const char** inputs, + const int* input_sizes, + int input_count, + char* output, + int output_size, + void* user_data) { + // Make sure we have exactly one input + if (input_count != 1) { + LOGE("Contrast: Incorrect input count! Expected 1 but got %d!", input_count); + return 0; + } + + // Make sure sizes match up + if (input_sizes[0] != output_size) { + LOGE("Contrast: Input-output sizes do not match up. %d vs. %d!", input_sizes[0], output_size); + return 0; + } + + // Get the input and output pointers + const char* input_ptr = inputs[0]; + char* output_ptr = output; + if (!input_ptr || !output_ptr) { + LOGE("Contrast: No input or output pointer found!"); + return 0; + } + + // Get the parameters + ContrastParameters* params = (ContrastParameters*)user_data; + const float contrast = params->contrast; + + // Run the contrast adjustment + int i; + for (i = 0; i < output_size; ++i) { + float px = *(input_ptr++) / 255.0; + px -= 0.5; + px *= contrast; + px += 0.5; + *(output_ptr++) = (char)(px > 1.0 ? 255.0 : (px < 0.0 ? 0.0 : px * 255.0)); + } + + return 1; +} + diff --git a/media/mca/filterpacks/native/imageproc/invert.c b/media/mca/filterpacks/native/imageproc/invert.c new file mode 100644 index 0000000..5938aac --- /dev/null +++ b/media/mca/filterpacks/native/imageproc/invert.c @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <android/log.h> + +int invert_process(const char** inputs, + const int* input_sizes, + int input_count, + char* output, + int output_size, + void* user_data) { + // Make sure we have exactly one input + if (input_count != 1) + return 0; + + // Make sure sizes match up + if (input_sizes[0] != output_size) + return 0; + + // Get the input and output pointers + const char* input_ptr = inputs[0]; + char* output_ptr = output; + if (!input_ptr || !output_ptr) + return 0; + + // Run the inversion + int i; + for (i = 0; i < output_size; ++i) + *(output_ptr++) = 255 - *(input_ptr++); + + return 1; +} + diff --git a/media/mca/filterpacks/native/imageproc/to_rgba.c b/media/mca/filterpacks/native/imageproc/to_rgba.c new file mode 100644 index 0000000..bf4db2a --- /dev/null +++ b/media/mca/filterpacks/native/imageproc/to_rgba.c @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> + +int gray_to_rgb_process(const char** inputs, + const int* input_sizes, + int input_count, + char* output, + int output_size, + void* user_data) { + // Make sure we have exactly one input + if (input_count != 1) + return 0; + + // Make sure sizes match up + if (input_sizes[0] != output_size/3) + return 0; + + // Get the input and output pointers + const char* input_ptr = inputs[0]; + char* output_ptr = output; + if (!input_ptr || !output_ptr) + return 0; + + // Run the conversion + int i; + for (i = 0; i < input_sizes[0]; ++i) { + *(output_ptr++) = *(input_ptr); + *(output_ptr++) = *(input_ptr); + *(output_ptr++) = *(input_ptr++); + } + + return 1; +} + +int rgba_to_rgb_process(const char** inputs, + const int* input_sizes, + int input_count, + char* output, + int output_size, + void* user_data) { + // Make sure we have exactly one input + if (input_count != 1) + return 0; + + // Make sure sizes match up + if (input_sizes[0]/4 != output_size/3) + return 0; + + // Get the input and output pointers + const char* input_ptr = inputs[0]; + char* output_ptr = output; + if (!input_ptr || !output_ptr) + return 0; + + // Run the conversion + int i; + for (i = 0; i < input_sizes[0] / 4; ++i) { + *(output_ptr++) = *(input_ptr++); + *(output_ptr++) = *(input_ptr++); + *(output_ptr++) = *(input_ptr++); + ++input_ptr; + } + + return 1; +} + +int gray_to_rgba_process(const char** inputs, + const int* input_sizes, + int input_count, + char* output, + int output_size, + void* user_data) { + // Make sure we have exactly one input + if (input_count != 1) + return 0; + + // Make sure sizes match up + if (input_sizes[0] != output_size/4) + return 0; + + // Get the input and output pointers + const char* input_ptr = inputs[0]; + char* output_ptr = output; + if (!input_ptr || !output_ptr) + return 0; + + // Run the conversion + int i; + for (i = 0; i < input_sizes[0]; ++i) { + *(output_ptr++) = *(input_ptr); + *(output_ptr++) = *(input_ptr); + *(output_ptr++) = *(input_ptr++); + *(output_ptr++) = 255; + } + + return 1; +} + +int rgb_to_rgba_process(const char** inputs, + const int* input_sizes, + int input_count, + char* output, + int output_size, + void* user_data) { + // Make sure we have exactly one input + if (input_count != 1) + return 0; + + // Make sure sizes match up + if (input_sizes[0]/3 != output_size/4) + return 0; + + // Get the input and output pointers + const char* input_ptr = inputs[0]; + char* output_ptr = output; + if (!input_ptr || !output_ptr) + return 0; + + // Run the conversion + int i; + for (i = 0; i < output_size / 4; ++i) { + *(output_ptr++) = *(input_ptr++); + *(output_ptr++) = *(input_ptr++); + *(output_ptr++) = *(input_ptr++); + *(output_ptr++) = 255; + } + + return 1; +} + |