summaryrefslogtreecommitdiffstats
path: root/src/com/android/camera/imageprocessor
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/camera/imageprocessor')
-rw-r--r--src/com/android/camera/imageprocessor/FrameProcessor.java335
-rw-r--r--src/com/android/camera/imageprocessor/PostProcessor.java448
-rw-r--r--src/com/android/camera/imageprocessor/filter/BeautificationFilter.java134
-rw-r--r--src/com/android/camera/imageprocessor/filter/ImageFilter.java74
-rw-r--r--src/com/android/camera/imageprocessor/filter/OptizoomFilter.java148
5 files changed, 1139 insertions, 0 deletions
diff --git a/src/com/android/camera/imageprocessor/FrameProcessor.java b/src/com/android/camera/imageprocessor/FrameProcessor.java
new file mode 100644
index 000000000..951479de9
--- /dev/null
+++ b/src/com/android/camera/imageprocessor/FrameProcessor.java
@@ -0,0 +1,335 @@
+/*
+Copyright (c) 2016, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.camera.imageprocessor;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ImageFormat;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.YuvImage;
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.ScriptIntrinsicYuvToRGB;
+import android.renderscript.Type;
+import android.util.Log;
+import android.util.Size;
+import android.view.Surface;
+
+import com.android.camera.CaptureModule;
+import com.android.camera.PhotoModule;
+import com.android.camera.imageprocessor.filter.BeautificationFilter;
+import com.android.camera.imageprocessor.filter.ImageFilter;
+import com.android.camera.util.CameraUtil;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+public class FrameProcessor {
+
+ private ImageReader mInputImageReader;
+ private Allocation mInputAllocation;
+ private Allocation mProcessAllocation;
+ private Allocation mOutputAllocation;
+
+ private HandlerThread mProcessingThread;
+ private Handler mProcessingHandler;
+ private HandlerThread mOutingThread;
+ private Handler mOutingHandler;
+
+ public ProcessingTask mTask;
+ private RenderScript mRs;
+ private Activity mActivity;
+ ScriptC_YuvToRgb mRsYuvToRGB;
+ ScriptC_rotator mRsRotator;
+ private Size mSize;
+ private Object mAllocationLock = new Object();
+ private boolean mIsAllocationEverUsed;
+ private ArrayList<ImageFilter> mPreviewFilters;
+ private ArrayList<ImageFilter> mFinalFilters;
+ private Surface mSurfaceAsItIs;
+ private boolean mIsActive = false;
+ public static final int FILTER_NONE = 0;
+ public static final int FILTER_MAKEUP = 1;
+ private CaptureModule mModule;
+
+ public FrameProcessor(Activity activity, CaptureModule module) {
+ mActivity = activity;
+ mModule = module;
+ mPreviewFilters = new ArrayList<ImageFilter>();
+ mFinalFilters = new ArrayList<ImageFilter>();
+ }
+
+ public void init(Size previewDim) {
+ mSize = previewDim;
+ synchronized (mAllocationLock) {
+ mRs = RenderScript.create(mActivity);
+ mRsYuvToRGB = new ScriptC_YuvToRgb(mRs);
+ mRsRotator = new ScriptC_rotator(mRs);
+ mInputImageReader = ImageReader.newInstance(mSize.getWidth(), mSize.getHeight(), ImageFormat.YUV_420_888, 8);
+
+ Type.Builder rgbTypeBuilder = new Type.Builder(mRs, Element.RGBA_8888(mRs));
+ rgbTypeBuilder.setX(mSize.getHeight());
+ rgbTypeBuilder.setY(mSize.getWidth());
+ mOutputAllocation = Allocation.createTyped(mRs, rgbTypeBuilder.create(),
+ Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_OUTPUT);
+
+ if (mProcessingThread == null) {
+ mProcessingThread = new HandlerThread("FrameProcessor");
+ mProcessingThread.start();
+ mProcessingHandler = new Handler(mProcessingThread.getLooper());
+ }
+
+ if (mOutingThread == null) {
+ mOutingThread = new HandlerThread("FrameOutingThread");
+ mOutingThread.start();
+ mOutingHandler = new Handler(mOutingThread.getLooper());
+ }
+
+ mTask = new ProcessingTask();
+ mInputImageReader.setOnImageAvailableListener(mTask, mProcessingHandler);
+ mIsAllocationEverUsed = false;
+ }
+ }
+
+ private void createAllocation(int width, int height) {
+ Type.Builder yuvTypeBuilder = new Type.Builder(mRs, Element.YUV(mRs));
+ yuvTypeBuilder.setX(width);
+ yuvTypeBuilder.setY(height);
+ yuvTypeBuilder.setYuvFormat(ImageFormat.NV21);
+ mInputAllocation = Allocation.createTyped(mRs, yuvTypeBuilder.create(), Allocation.USAGE_SCRIPT);
+ Type.Builder nv21TypeBuilder = new Type.Builder(mRs, Element.U8(mRs));
+ nv21TypeBuilder.setX(width * height * 3 / 2);
+ mProcessAllocation = Allocation.createTyped(mRs, nv21TypeBuilder.create(), Allocation.USAGE_SCRIPT);
+ mRsRotator.set_gIn(mInputAllocation);
+ mRsRotator.set_gOut(mProcessAllocation);
+ mRsRotator.set_width(width);
+ mRsRotator.set_height(height);
+ mRsYuvToRGB.set_gIn(mProcessAllocation);
+ mRsYuvToRGB.set_width(height);
+ mRsYuvToRGB.set_height(width);
+ }
+
+ public ArrayList<ImageFilter> getFrameFilters() {
+ return mFinalFilters;
+ }
+
+ private void cleanFilterSet() {
+ if(mPreviewFilters != null) {
+ for (ImageFilter filter : mPreviewFilters) {
+ filter.deinit();
+ }
+ }
+ if(mFinalFilters != null) {
+ for (ImageFilter filter : mFinalFilters) {
+ filter.deinit();
+ }
+ }
+ mPreviewFilters = new ArrayList<ImageFilter>();
+ mFinalFilters = new ArrayList<ImageFilter>();
+ }
+
+ public void onOpen(ArrayList<Integer> filterIds) {
+ mIsActive = true;
+ synchronized (mAllocationLock) {
+ cleanFilterSet();
+ if (filterIds != null) {
+ for (Integer i : filterIds) {
+ addFilter(i.intValue());
+ }
+ }
+ }
+ }
+
+ private void addFilter(int filterId) {
+ if(filterId == FILTER_MAKEUP) {
+ ImageFilter filter = new BeautificationFilter(mModule);
+ if(filter.isSupported()) {
+ mPreviewFilters.add(filter);
+ mFinalFilters.add(filter);
+ }
+ }
+ }
+
+ public void onClose() {
+ mIsActive = false;
+ synchronized (mAllocationLock) {
+ if (mIsAllocationEverUsed) {
+ if (mInputAllocation != null) {
+ mInputAllocation.destroy();
+ }
+ if (mOutputAllocation != null) {
+ mOutputAllocation.destroy();
+ }
+ if (mProcessAllocation != null) {
+ mProcessAllocation.destroy();
+ }
+ }
+ if (mRs != null) {
+ mRs.destroy();
+ }
+ mRs = null;
+ mProcessAllocation = null;
+ mOutputAllocation = null;
+ mInputAllocation = null;
+ }
+ if (mProcessingThread != null) {
+ mProcessingThread.quitSafely();
+ try {
+ mProcessingThread.join();
+ mProcessingThread = null;
+ mProcessingHandler = null;
+ } catch (InterruptedException e) {
+ }
+ }
+ if (mOutingThread != null) {
+ mOutingThread.quitSafely();
+ try {
+ mOutingThread.join();
+ mOutingThread = null;
+ mOutingHandler = null;
+ } catch (InterruptedException e) {
+ }
+ }
+ for(ImageFilter filter : mPreviewFilters) {
+ filter.deinit();
+ }
+ for(ImageFilter filter : mFinalFilters) {
+ filter.deinit();
+ }
+ }
+
+ public Surface getInputSurface() {
+ if(mPreviewFilters.size() == 0) {
+ return mSurfaceAsItIs;
+ }
+ synchronized (mAllocationLock) {
+ if (mInputImageReader == null)
+ return null;
+ return mInputImageReader.getSurface();
+ }
+ }
+
+ public boolean isFrameFilterEnabled() {
+ if(mPreviewFilters.size() == 0) {
+ return false;
+ }
+ return true;
+ }
+
+ public void setOutputSurface(Surface surface) {
+ if(mPreviewFilters.size() == 0) {
+ mSurfaceAsItIs = surface;
+ } else {
+ mOutputAllocation.setSurface(surface);
+ }
+ }
+
+ class ProcessingTask implements Runnable, ImageReader.OnImageAvailableListener {
+ byte[] yvuBytes = null;
+ int ySize;
+ int stride;
+ int height;
+
+ public ProcessingTask() {
+ }
+
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ synchronized (mAllocationLock) {
+ if(mOutputAllocation == null)
+ return;
+ try {
+ Image image = reader.acquireLatestImage();
+ if(image == null)
+ return;
+ if(!mIsActive) {
+ image.close();
+ return;
+ }
+ mIsAllocationEverUsed = true;
+ ByteBuffer bY = image.getPlanes()[0].getBuffer();
+ ByteBuffer bVU = image.getPlanes()[2].getBuffer();
+ if(yvuBytes == null) {
+ stride = image.getPlanes()[0].getRowStride();
+ height = mSize.getHeight();
+ ySize = stride * mSize.getHeight();
+ yvuBytes = new byte[ySize*3/2];
+ }
+ //Start processing yvu buf
+ for (ImageFilter filter : mPreviewFilters) {
+ filter.init(mSize.getWidth(), mSize.getHeight(), stride, stride);
+ filter.addImage(bY, bVU, 0, new Boolean(true));
+ }
+ //End processing yvu buf
+ bY.get(yvuBytes, 0, bY.remaining());
+ bVU.get(yvuBytes, ySize, bVU.remaining());
+ image.close();
+ mOutingHandler.post(this);
+ } catch (IllegalStateException e) {
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ synchronized (mAllocationLock) {
+ if(!mIsActive) {
+ return;
+ }
+ if(mInputAllocation == null) {
+ createAllocation(stride, height);
+ }
+ mInputAllocation.copyFrom(yvuBytes);
+ mRsRotator.forEach_rotate90andMerge(mInputAllocation);
+ mRsYuvToRGB.forEach_nv21ToRgb(mOutputAllocation);
+ mOutputAllocation.ioSend();
+ }
+ }
+ }
+
+ private native int nativeRotateNV21(ByteBuffer inBuf, int imageWidth, int imageHeight, int degree, ByteBuffer outBuf);
+
+ private native int nativeNV21toRgb(ByteBuffer yvuBuf, ByteBuffer rgbBuf, int width, int height);
+
+ static {
+ System.loadLibrary("jni_imageutil");
+ }
+}
+
diff --git a/src/com/android/camera/imageprocessor/PostProcessor.java b/src/com/android/camera/imageprocessor/PostProcessor.java
new file mode 100644
index 000000000..a126e8817
--- /dev/null
+++ b/src/com/android/camera/imageprocessor/PostProcessor.java
@@ -0,0 +1,448 @@
+/*
+Copyright (c) 2016, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.android.camera.imageprocessor;
+
+import android.content.ContentResolver;
+import android.graphics.ImageFormat;
+import android.graphics.Rect;
+import android.graphics.YuvImage;
+import android.hardware.camera2.CaptureRequest;
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.camera.CameraActivity;
+import com.android.camera.CaptureModule;
+import com.android.camera.MediaSaveService;
+import com.android.camera.PhotoModule;
+import com.android.camera.SettingsManager;
+import com.android.camera.imageprocessor.filter.OptizoomFilter;
+import com.android.camera.ui.RotateTextToast;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import com.android.camera.imageprocessor.filter.ImageFilter;
+
+public class PostProcessor implements ImageReader.OnImageAvailableListener{
+
+ private CaptureModule mController;
+
+ private static final String TAG = "PostProcessor";
+ public static final int FILTER_NONE = 0;
+ public static final int FILTER_OPTIZOOM = 1;
+ public static final int FILTER_MAX = 2;
+
+ private int mCurrentNumImage = 0;
+ private ImageFilter mFilter;
+ private int mFilterIndex;
+ private HandlerThread mHandlerThread;
+ private ProcessorHandler mHandler;
+ private CameraActivity mActivity;
+ private int mWidth;
+ private int mHeight;
+ private int mStride;
+ private Object lock = new Object();
+ private ImageFilter.ResultImage mDefaultResultImage; //This is used only no filter is chosen.
+ private Image[] mImages;
+ private PhotoModule.NamedImages mNamedImages;
+ private WatchdogThread mWatchdog;
+
+ //This is for the debug feature.
+ private static boolean DEBUG_FILTER = true; //TODO: This has to be false before releasing.
+ private ImageFilter.ResultImage mDebugResultImage;
+
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ try {
+ Image image = reader.acquireNextImage();
+ addImage(image);
+ if (isReadyToProcess()) {
+ long captureStartTime = System.currentTimeMillis();
+ mNamedImages.nameNewImage(captureStartTime);
+ PhotoModule.NamedImages.NamedEntity name = mNamedImages.getNextNameEntity();
+ String title = (name == null) ? null : name.title;
+ long date = (name == null) ? -1 : name.date;
+ processImage(title, date, mController.getMediaSavedListener(), mActivity.getContentResolver());
+ }
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Max images has been already acquired. ");
+ }
+ }
+
+ enum STATUS {
+ DEINIT,
+ INIT,
+ BUSY
+ }
+ private STATUS mStatus = STATUS.DEINIT;
+
+ public PostProcessor(CameraActivity activity, CaptureModule module) {
+ mController = module;
+ mActivity = activity;
+ mNamedImages = new PhotoModule.NamedImages();
+
+ }
+
+ public boolean isItBusy() {
+ if(mStatus == STATUS.BUSY)
+ return true;
+ return false;
+ }
+
+ public List<CaptureRequest> setRequiredImages(CaptureRequest.Builder builder) {
+ if(mFilter == null) {
+ List<CaptureRequest> list = new ArrayList<CaptureRequest>();
+ list.add(builder.build());
+ return list;
+ } else {
+ return mFilter.setRequiredImages(builder);
+ }
+ }
+
+ public boolean isFilterOn() {
+ if(mFilter != null) {
+ return true;
+ }
+ if(mController.getFrameFilters().size() != 0) {
+ return true;
+ }
+ return false;
+ }
+
+ public void onOpen(int postFilterId) {
+ setFilter(postFilterId);
+ startBackgroundThread();
+
+ }
+
+ public int getFilterIndex() {
+ return mFilterIndex;
+ }
+
+ public void onClose() {
+ synchronized (lock) {
+ if(mHandler != null) {
+ mHandler.setInActive();
+ }
+ stopBackgroundThread();
+ }
+ setFilter(FILTER_NONE);
+ }
+
+ private void startBackgroundThread() {
+ mHandlerThread = new HandlerThread("PostProcessorThread");
+ mHandlerThread.start();
+ mHandler = new ProcessorHandler(mHandlerThread.getLooper());
+
+ mWatchdog = new WatchdogThread();
+ mWatchdog.start();
+ }
+
+ class WatchdogThread extends Thread {
+ private boolean isAlive = true;
+ private boolean isMonitor = false;
+ private int counter = 0;
+ public void run() {
+ while(isAlive) {
+ try {
+ Thread.sleep(200);
+ }catch(InterruptedException e) {
+ }
+ if(isMonitor) {
+ counter++;
+ if(counter >= 40) { //This is 4 seconds.
+ bark();
+ break;
+ }
+ }
+ }
+
+ }
+
+ public void startMonitor() {
+ isMonitor = true;
+ }
+
+ public void stopMonitor() {
+ isMonitor = false;
+ counter = 0;
+ }
+
+ public void kill() {
+ isAlive = false;
+ }
+ private void bark() {
+ Log.e(TAG, "It takes too long to get the images and process the filter!");
+ int index = getFilterIndex();
+ setFilter(FILTER_NONE);
+ setFilter(index);
+ }
+ }
+
+ class ProcessorHandler extends Handler {
+ boolean isRunning;
+
+ public ProcessorHandler(Looper looper) {
+ super(looper);
+ isRunning = true;
+ }
+
+ public void setInActive() {
+ isRunning = false;
+ }
+ }
+
+ private void stopBackgroundThread() {
+ if (mHandlerThread != null) {
+ mHandlerThread.quitSafely();
+ try {
+ mHandlerThread.join();
+ } catch (InterruptedException e) {
+ }
+ mHandlerThread = null;
+ mHandler = null;
+ }
+ if(mWatchdog != null) {
+ mWatchdog.kill();
+ mWatchdog = null;
+ }
+ clear();
+ }
+
+ public boolean setFilter(int index) {
+ if(index < 0 || index >= FILTER_MAX) {
+ Log.e(TAG, "Invalid scene filter ID");
+ return false;
+ }
+ synchronized (lock) {
+ if (mFilter != null) {
+ mFilter.deinit();
+ }
+ mStatus = STATUS.DEINIT;
+ switch (index) {
+ case FILTER_NONE:
+ mFilter = null;
+ break;
+ case FILTER_OPTIZOOM:
+ mFilter = new OptizoomFilter(mController);
+ break;
+ }
+ }
+
+ if(mFilter != null && !mFilter.isSupported()) {
+ final String filterName = mFilter.getStringName();
+ mFilter = null;
+ mActivity.runOnUiThread(new Runnable() {
+ public void run() {
+ RotateTextToast.makeText(mActivity, filterName+" is not supported. ", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ if(mFilter == null) {
+ mFilterIndex = FILTER_NONE;
+ return false;
+ }
+ mFilterIndex = index;
+ mImages = new Image[mFilter.getNumRequiredImage()];
+ return true;
+ }
+
+ private boolean isReadyToProcess() {
+ synchronized (lock) {
+ if (mFilter == null) {
+ return true;
+ }
+ if (mCurrentNumImage >= mFilter.getNumRequiredImage()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void addImage(final Image image) {
+ if(mHandler == null || !mHandler.isRunning) {
+ return;
+ }
+ final ProcessorHandler handler = mHandler;
+ if (mStatus == STATUS.DEINIT) {
+ mWidth = image.getWidth();
+ mHeight = image.getHeight();
+ mStride = image.getPlanes()[0].getRowStride();
+ mStatus = STATUS.INIT;
+ mHandler.post(new Runnable() {
+ public void run() {
+ synchronized (lock) {
+ if(!handler.isRunning) {
+ return;
+ }
+ if(mFilter == null) {
+ //Nothing here we have to do if filter is not chosen.
+ } else {
+ mFilter.init(mWidth, mHeight, mStride, mStride);
+ }
+ }
+ }
+ });
+ }
+ if(mCurrentNumImage == 0) {
+ mStatus = STATUS.BUSY;
+ if(mWatchdog != null) {
+ mWatchdog.startMonitor();
+ }
+ }
+ if(mFilter != null && mCurrentNumImage >= mFilter.getNumRequiredImage()) {
+ return;
+ }
+ final int numImage = mCurrentNumImage;
+ mCurrentNumImage++;
+ if(mHandler == null) {
+ return;
+ }
+ mHandler.post(new Runnable() {
+ public void run() {
+ synchronized (lock) {
+ if(!handler.isRunning) {
+ return;
+ }
+ ByteBuffer yBuf = image.getPlanes()[0].getBuffer();
+ ByteBuffer vuBuf = image.getPlanes()[2].getBuffer();
+ if(mFilter != null && DEBUG_FILTER && numImage == 0) {
+ mDebugResultImage = new ImageFilter.ResultImage(ByteBuffer.allocateDirect(mStride * mHeight*3/2),
+ new Rect(0, 0, mWidth, mHeight), mWidth, mHeight, mStride);
+ yBuf.get(mDebugResultImage.outBuffer.array(), 0, yBuf.remaining());
+ vuBuf.get(mDebugResultImage.outBuffer.array(), mStride * mHeight, vuBuf.remaining());
+ yBuf.rewind();
+ vuBuf.rewind();
+ }
+ if(mFilter == null) {
+ mDefaultResultImage = new ImageFilter.ResultImage(ByteBuffer.allocateDirect(mStride * mHeight*3/2),
+ new Rect(0, 0, mWidth, mHeight), mWidth, mHeight, mStride);
+ yBuf.get(mDefaultResultImage.outBuffer.array(), 0, yBuf.remaining());
+ vuBuf.get(mDefaultResultImage.outBuffer.array(), mStride*mHeight, vuBuf.remaining());
+ image.close();
+ } else {
+ mFilter.addImage(image.getPlanes()[0].getBuffer(),
+ image.getPlanes()[2].getBuffer(), numImage, null);
+ mImages[numImage] = image;
+ }
+ }
+ }
+ });
+ }
+
+ private void clear() {
+ mCurrentNumImage = 0;
+ }
+
+ private void processImage(final String title, final long date,
+ final MediaSaveService.OnMediaSavedListener mediaSavedListener,
+ final ContentResolver contentResolver) {
+ if(mHandler == null || !mHandler.isRunning) {
+ return;
+ }
+ final ProcessorHandler handler = mHandler;
+ mHandler.post(new Runnable() {
+ public void run() {
+ byte[] bytes;
+ ImageFilter.ResultImage resultImage = null;
+ synchronized (lock) {
+ if (!handler.isRunning) {
+ return;
+ }
+ if (mFilter == null) { //In case no post filter is chosen
+ resultImage = mDefaultResultImage;
+ } else {
+ resultImage = mFilter.processImage();
+ for (int i = 0; i < mImages.length; i++) {
+ if(mImages[i] != null) {
+ mImages[i].close();
+ mImages[i] = null;
+ }
+ }
+ }
+ //Start processing FrameProcessor filter as well
+ for (ImageFilter filter : mController.getFrameFilters()) {
+ filter.init(resultImage.width, resultImage.height, resultImage.stride, resultImage.stride);
+ filter.addImage(resultImage.outBuffer, null, 0, new Boolean(false));
+ }
+ //End processing FrameProessor filter
+ clear();
+ mStatus = STATUS.INIT;
+ if(mWatchdog != null) {
+ mWatchdog.stopMonitor();
+ }
+ if((resultImage.outRoi.left + resultImage.outRoi.width() > resultImage.width) ||
+ (resultImage.outRoi.top + resultImage.outRoi.height() > resultImage.height)
+ ) {
+ Log.e(TAG, "Processed outRoi is not within picture range");
+ } else {
+ if(mFilter != null && DEBUG_FILTER) {
+ bytes = nv21ToJpeg(mDebugResultImage);
+ mActivity.getMediaSaveService().addImage(
+ bytes, title + "_beforeApplyingFilter", date, null, mDebugResultImage.outRoi.width(), mDebugResultImage.outRoi.height(),
+ 0, null, mediaSavedListener, contentResolver, "jpeg");
+ }
+ bytes = nv21ToJpeg(resultImage);
+ mController.updateThumbnailJpegData(bytes);
+ mActivity.getMediaSaveService().addImage(
+ bytes, title, date, null, resultImage.outRoi.width(), resultImage.outRoi.height(),
+ 0, null, mediaSavedListener, contentResolver, "jpeg");
+ }
+ }
+ }
+ });
+ }
+
+ private byte[] nv21ToJpeg(ImageFilter.ResultImage resultImage) {
+ BitmapOutputStream bos = new BitmapOutputStream(1024);
+ YuvImage im = new YuvImage(resultImage.outBuffer.array(), ImageFormat.NV21,
+ resultImage.width, resultImage.height, new int[]{resultImage.stride, resultImage.stride});
+ im.compressToJpeg(resultImage.outRoi, 50, bos);
+ return bos.getArray();
+ }
+
+ private class BitmapOutputStream extends ByteArrayOutputStream {
+ public BitmapOutputStream(int size) {
+ super(size);
+ }
+
+ public byte[] getArray() {
+ return buf;
+ }
+ }
+
+
+}
diff --git a/src/com/android/camera/imageprocessor/filter/BeautificationFilter.java b/src/com/android/camera/imageprocessor/filter/BeautificationFilter.java
new file mode 100644
index 000000000..6ec9376d0
--- /dev/null
+++ b/src/com/android/camera/imageprocessor/filter/BeautificationFilter.java
@@ -0,0 +1,134 @@
+/*
+Copyright (c) 2016, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.android.camera.imageprocessor.filter;
+
+import android.graphics.Rect;
+import android.hardware.Camera;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.Face;
+import android.util.Log;
+import android.util.Size;
+
+import com.android.camera.CaptureModule;
+import com.android.camera.ui.FilmstripBottomControls;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+public class BeautificationFilter implements ImageFilter {
+
+ int mWidth;
+ int mHeight;
+ int mStrideY;
+ int mStrideVU;
+ private CaptureModule mModule;
+ private static boolean DEBUG = false;
+ private static String TAG = "BeautificationFilter";
+ private static boolean mIsSupported = false;
+
+ public BeautificationFilter(CaptureModule module) {
+ mModule = module;
+ }
+
+ @Override
+ public List<CaptureRequest> setRequiredImages(CaptureRequest.Builder builder) {
+ return null;
+ }
+
+ @Override
+ public String getStringName() {
+ return "BeautificationFilter";
+ }
+
+ @Override
+ public int getNumRequiredImage() {
+ return 0;
+ }
+
+ @Override
+ public void init(int width, int height, int strideY, int strideVU) {
+ mWidth = width;
+ mHeight = height;
+ mStrideY = strideY;
+ mStrideVU = strideVU;
+ }
+
+ @Override
+ public void deinit() {
+
+ }
+
+ @Override
+ public void addImage(ByteBuffer bY, ByteBuffer bVU, int imageNum, Object isPreview) {
+ Rect back = mModule.getCameraRegion();
+ Face[] faces;
+ if(((Boolean)isPreview).booleanValue()) {
+ faces = mModule.getPreviewFaces();
+ } else {
+ faces = mModule.getStickyFaces();
+ }
+ float widthRatio = (float)mWidth/back.width();
+ float heightRatio = (float)mHeight/back.height();
+ if(faces == null || faces.length == 0)
+ return;
+ Rect rect = faces[0].getBounds();
+ int value = nativeBeautificationProcess(bY, bVU, mWidth, mHeight, mStrideY,
+ (int)(rect.left*widthRatio), (int)(rect.top*heightRatio),
+ (int)(rect.right*widthRatio), (int)(rect.bottom*heightRatio));
+ if(DEBUG && value < 0) {
+ if(value == -1) {
+ Log.d(TAG, "library initialization is failed.");
+ } else if(value == -2) {
+ Log.d(TAG, "No face is recognized");
+ }
+ }
+ }
+
+ @Override
+ public ResultImage processImage() {
+ return null;
+ }
+
+ @Override
+ public boolean isSupported() {
+ return mIsSupported;
+ }
+
+ private native int nativeBeautificationProcess(ByteBuffer yB, ByteBuffer vuB,
+ int width, int height, int stride, int fleft, int ftop, int fright, int fbottom);
+
+ static {
+ try {
+ System.loadLibrary("jni_makeup");
+ mIsSupported = true;
+ }catch(UnsatisfiedLinkError e) {
+ mIsSupported = false;
+ }
+ }
+}
diff --git a/src/com/android/camera/imageprocessor/filter/ImageFilter.java b/src/com/android/camera/imageprocessor/filter/ImageFilter.java
new file mode 100644
index 000000000..e62d9b30a
--- /dev/null
+++ b/src/com/android/camera/imageprocessor/filter/ImageFilter.java
@@ -0,0 +1,74 @@
+/*
+Copyright (c) 2016, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.android.camera.imageprocessor.filter;
+
+import android.graphics.Rect;
+import android.hardware.camera2.CaptureRequest;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+public interface ImageFilter {
+
+ /* Return the number of required images to process*/
+ List<CaptureRequest> setRequiredImages(CaptureRequest.Builder builder);
+
+ String getStringName();
+
+ int getNumRequiredImage();
+
+ void init(int width, int height, int strideY, int strideVU);
+
+ /* Free all buffer */
+ void deinit();
+
+ /* Adding the image to process */
+ void addImage(ByteBuffer bY, ByteBuffer bVU, int imageNum, Object param);
+
+ /* Processing all the added images and return roi*/
+ ResultImage processImage();
+
+ boolean isSupported();
+
+ class ResultImage {
+ public ByteBuffer outBuffer;
+ public Rect outRoi;
+ public int width;
+ public int height;
+ public int stride;
+
+ public ResultImage(ByteBuffer buf, Rect roi, int width, int height, int stride) {
+ outBuffer = buf;
+ outRoi = roi;
+ this.width = width;
+ this.height = height;
+ this.stride = stride;
+ }
+ }
+}
diff --git a/src/com/android/camera/imageprocessor/filter/OptizoomFilter.java b/src/com/android/camera/imageprocessor/filter/OptizoomFilter.java
new file mode 100644
index 000000000..4773418de
--- /dev/null
+++ b/src/com/android/camera/imageprocessor/filter/OptizoomFilter.java
@@ -0,0 +1,148 @@
+/*
+Copyright (c) 2016, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.android.camera.imageprocessor.filter;
+
+import android.graphics.Rect;
+import android.hardware.camera2.CaptureRequest;
+import android.util.Log;
+
+import com.android.camera.CaptureModule;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+public class OptizoomFilter implements ImageFilter{
+ public static final int NUM_REQUIRED_IMAGE = 8;
+ private int mWidth;
+ private int mHeight;
+ private int mStrideY;
+ private int mStrideVU;
+ private static String TAG = "OptizoomFilter";
+ private static final boolean DEBUG = true; //TODO: Have to be false before releasing.
+ private int temp;
+ private static boolean mIsSupported = true;
+ private ByteBuffer mOutBuf;
+ private CaptureModule mModule;
+
+ private static void Log(String msg) {
+ if(DEBUG) {
+ Log.d(TAG, msg);
+ }
+ }
+
+ public OptizoomFilter(CaptureModule module) {
+ mModule = module;
+ }
+
+ @Override
+ public List<CaptureRequest> setRequiredImages(CaptureRequest.Builder builder) {
+ List<CaptureRequest> list = new ArrayList<CaptureRequest>();
+ for(int i=0; i < NUM_REQUIRED_IMAGE; i++) {
+ list.add(builder.build());
+ }
+ return list;
+ }
+
+ @Override
+ public String getStringName() {
+ return "OptizoomFilter";
+ }
+
+ @Override
+ public int getNumRequiredImage() {
+ return NUM_REQUIRED_IMAGE;
+ }
+
+ @Override
+ public void init(int width, int height, int strideY, int strideVU) {
+ Log("init");
+ mWidth = width/2*2;
+ mHeight = height/2*2;
+ mStrideY = strideY/2*2;
+ mStrideVU = strideVU/2*2;
+ mOutBuf = ByteBuffer.allocate(mStrideY*mHeight*6); // YUV Buffer to hold (mWidth*2) X (mHeight*2)
+ Log("width: "+mWidth+" height: "+mHeight+" strideY: "+mStrideY+" strideVU: "+mStrideVU);
+ nativeInit(mWidth, mHeight, mStrideY, mStrideVU,
+ 0, 0, mWidth, mHeight, NUM_REQUIRED_IMAGE);
+ }
+
+ @Override
+ public void deinit() {
+ Log("deinit");
+ mOutBuf = null;
+ nativeDeinit();
+ }
+
+ @Override
+ public void addImage(ByteBuffer bY, ByteBuffer bVU, int imageNum, Object param) {
+ Log("addImage");
+ int yActualSize = bY.remaining();
+ int vuActualSize = bVU.remaining();
+ nativeAddImage(bY, bVU, yActualSize, vuActualSize, imageNum);
+ }
+
+ @Override
+ public ResultImage processImage() {
+ Log("processImage " + mModule.getZoomValue());
+ int[] roi = new int[4];
+ int status = nativeProcessImage(mOutBuf.array(), mModule.getZoomValue(), roi);
+ Log("processImage done");
+ if(status < 0) { //In failure case, library will return the first image as it is.
+ Log.w(TAG, "Fail to process the optizoom. It only processes when zoomValue >= 1.5f");
+ return new ResultImage(mOutBuf, new Rect(roi[0], roi[1], roi[0]+roi[2], roi[1] + roi[3]), mWidth, mHeight, mStrideY);
+ } else { //In success case, it will return twice bigger width and height.
+ return new ResultImage(mOutBuf, new Rect(roi[0], roi[1], roi[0]+roi[2], roi[1] + roi[3]), mWidth*2, mHeight*2, mStrideY*2);
+ }
+ }
+
+ @Override
+ public boolean isSupported() {
+ return mIsSupported;
+ }
+
+ public static boolean isSupportedStatic() {
+ return mIsSupported;
+ }
+
+ private native int nativeInit(int width, int height, int yStride, int vuStride,
+ int roiX, int roiY, int roiW, int roiH, int numImages);
+ private native int nativeDeinit();
+ private native int nativeAddImage(ByteBuffer yB, ByteBuffer vuB, int ySize, int vuSize, int imageNum);
+ private native int nativeProcessImage(byte[] buffer, float zoomLvl, int[] roi);
+
+ static {
+ try {
+ System.loadLibrary("jni_optizoom");
+ mIsSupported = true;
+ }catch(UnsatisfiedLinkError e) {
+ mIsSupported = false;
+ }
+ }
+}