diff options
Diffstat (limited to 'src/com/android/gallery3d/filtershow/pipeline')
17 files changed, 2796 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/filtershow/pipeline/Buffer.java b/src/com/android/gallery3d/filtershow/pipeline/Buffer.java new file mode 100644 index 000000000..744451229 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/Buffer.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2013 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 com.android.gallery3d.filtershow.pipeline; + +import android.graphics.Bitmap; +import android.support.v8.renderscript.Allocation; +import android.support.v8.renderscript.RenderScript; + +public class Buffer { + private static final String LOGTAG = "Buffer"; + private Bitmap mBitmap; + private Allocation mAllocation; + private boolean mUseAllocation = false; + private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888; + private ImagePreset mPreset; + + public Buffer(Bitmap bitmap) { + RenderScript rs = CachingPipeline.getRenderScriptContext(); + if (bitmap != null) { + mBitmap = bitmap.copy(BITMAP_CONFIG, true); + } + if (mUseAllocation) { + // TODO: recreate the allocation when the RS context changes + mAllocation = Allocation.createFromBitmap(rs, mBitmap, + Allocation.MipmapControl.MIPMAP_NONE, + Allocation.USAGE_SHARED | Allocation.USAGE_SCRIPT); + } + } + + public void setBitmap(Bitmap bitmap) { + mBitmap = bitmap.copy(BITMAP_CONFIG, true); + } + + public Bitmap getBitmap() { + return mBitmap; + } + + public Allocation getAllocation() { + return mAllocation; + } + + public void sync() { + if (mUseAllocation) { + mAllocation.copyTo(mBitmap); + } + } + + public ImagePreset getPreset() { + return mPreset; + } + + public void setPreset(ImagePreset preset) { + if ((mPreset == null) || (!mPreset.same(preset))) { + mPreset = new ImagePreset(preset); + } else { + mPreset.updateWith(preset); + } + } +} + diff --git a/src/com/android/gallery3d/filtershow/pipeline/CacheProcessing.java b/src/com/android/gallery3d/filtershow/pipeline/CacheProcessing.java new file mode 100644 index 000000000..e0269e9bb --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/CacheProcessing.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2013 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 com.android.gallery3d.filtershow.pipeline; + +import android.graphics.Bitmap; +import android.util.Log; +import com.android.gallery3d.filtershow.filters.FilterRepresentation; + +import java.util.Vector; + +public class CacheProcessing { + private static final String LOGTAG = "CacheProcessing"; + private static final boolean DEBUG = false; + private Vector<CacheStep> mSteps = new Vector<CacheStep>(); + + static class CacheStep { + FilterRepresentation representation; + Bitmap cache; + } + + public Bitmap process(Bitmap originalBitmap, + Vector<FilterRepresentation> filters, + FilterEnvironment environment) { + + if (filters.size() == 0) { + return originalBitmap; + } + + // New set of filters, let's clear the cache and rebuild it. + if (filters.size() != mSteps.size()) { + mSteps.clear(); + for (int i = 0; i < filters.size(); i++) { + FilterRepresentation representation = filters.elementAt(i); + CacheStep step = new CacheStep(); + step.representation = representation.copy(); + mSteps.add(step); + } + } + + if (DEBUG) { + displayFilters(filters); + } + + // First, let's find how similar we are in our cache + // compared to the current list of filters + int similarUpToIndex = -1; + for (int i = 0; i < filters.size(); i++) { + FilterRepresentation representation = filters.elementAt(i); + CacheStep step = mSteps.elementAt(i); + boolean similar = step.representation.equals(representation); + if (similar) { + similarUpToIndex = i; + } else { + break; + } + } + if (DEBUG) { + Log.v(LOGTAG, "similar up to index " + similarUpToIndex); + } + + // Now, let's get the earliest cached result in our pipeline + Bitmap cacheBitmap = null; + int findBaseImageIndex = similarUpToIndex; + if (findBaseImageIndex > -1) { + while (findBaseImageIndex > 0 + && mSteps.elementAt(findBaseImageIndex).cache == null) { + findBaseImageIndex--; + } + cacheBitmap = mSteps.elementAt(findBaseImageIndex).cache; + } + boolean emptyStack = false; + if (cacheBitmap == null) { + emptyStack = true; + // Damn, it's an empty stack, we have to start from scratch + // TODO: use a bitmap cache + RS allocation instead of Bitmap.copy() + cacheBitmap = originalBitmap.copy(Bitmap.Config.ARGB_8888, true); + if (findBaseImageIndex > -1) { + FilterRepresentation representation = filters.elementAt(findBaseImageIndex); + if (representation.getFilterType() != FilterRepresentation.TYPE_GEOMETRY) { + cacheBitmap = environment.applyRepresentation(representation, cacheBitmap); + } + mSteps.elementAt(findBaseImageIndex).representation = representation.copy(); + mSteps.elementAt(findBaseImageIndex).cache = cacheBitmap; + } + if (DEBUG) { + Log.v(LOGTAG, "empty stack"); + } + } + + // Ok, so sadly the earliest cached result is before the index we want. + // We have to rebuild a new result for this position, and then cache it. + if (findBaseImageIndex != similarUpToIndex) { + if (DEBUG) { + Log.v(LOGTAG, "rebuild cacheBitmap from " + findBaseImageIndex + + " to " + similarUpToIndex); + } + // rebuild the cache image for this step + if (!emptyStack) { + cacheBitmap = cacheBitmap.copy(Bitmap.Config.ARGB_8888, true); + } else { + // if it was an empty stack, we already applied it + findBaseImageIndex ++; + } + for (int i = findBaseImageIndex; i <= similarUpToIndex; i++) { + FilterRepresentation representation = filters.elementAt(i); + if (representation.getFilterType() != FilterRepresentation.TYPE_GEOMETRY) { + cacheBitmap = environment.applyRepresentation(representation, cacheBitmap); + } + if (DEBUG) { + Log.v(LOGTAG, " - " + i + " => apply " + representation.getName()); + } + } + // Let's cache it! + mSteps.elementAt(similarUpToIndex).cache = cacheBitmap; + } + + if (DEBUG) { + Log.v(LOGTAG, "process pipeline from " + similarUpToIndex + + " to " + (filters.size() - 1)); + } + + // Now we are good to go, let's use the cacheBitmap as a starting point + for (int i = similarUpToIndex + 1; i < filters.size(); i++) { + FilterRepresentation representation = filters.elementAt(i); + CacheStep currentStep = mSteps.elementAt(i); + cacheBitmap = cacheBitmap.copy(Bitmap.Config.ARGB_8888, true); + if (representation.getFilterType() != FilterRepresentation.TYPE_GEOMETRY) { + cacheBitmap = environment.applyRepresentation(representation, cacheBitmap); + } + currentStep.representation = representation.copy(); + currentStep.cache = cacheBitmap; + if (DEBUG) { + Log.v(LOGTAG, " - " + i + " => apply " + representation.getName()); + } + } + + if (DEBUG) { + Log.v(LOGTAG, "now let's cleanup the cache..."); + displayNbBitmapsInCache(); + } + + // Let's see if we can cleanup the cache for unused bitmaps + for (int i = 0; i < similarUpToIndex; i++) { + CacheStep currentStep = mSteps.elementAt(i); + currentStep.cache = null; + } + + if (DEBUG) { + Log.v(LOGTAG, "cleanup done..."); + displayNbBitmapsInCache(); + } + return cacheBitmap; + } + + private void displayFilters(Vector<FilterRepresentation> filters) { + Log.v(LOGTAG, "------>>>"); + for (int i = 0; i < filters.size(); i++) { + FilterRepresentation representation = filters.elementAt(i); + CacheStep step = mSteps.elementAt(i); + boolean similar = step.representation.equals(representation); + Log.v(LOGTAG, "[" + i + "] - " + representation.getName() + + " similar rep ? " + (similar ? "YES" : "NO") + + " -- bitmap: " + step.cache); + } + Log.v(LOGTAG, "<<<------"); + } + + private void displayNbBitmapsInCache() { + int nbBitmapsCached = 0; + for (int i = 0; i < mSteps.size(); i++) { + CacheStep step = mSteps.elementAt(i); + if (step.cache != null) { + nbBitmapsCached++; + } + } + Log.v(LOGTAG, "nb bitmaps in cache: " + nbBitmapsCached + " / " + mSteps.size()); + } + +} diff --git a/src/com/android/gallery3d/filtershow/pipeline/CachingPipeline.java b/src/com/android/gallery3d/filtershow/pipeline/CachingPipeline.java new file mode 100644 index 000000000..fc0d6ce49 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/CachingPipeline.java @@ -0,0 +1,469 @@ +/* + * Copyright (C) 2013 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 com.android.gallery3d.filtershow.pipeline; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.support.v8.renderscript.Allocation; +import android.support.v8.renderscript.RenderScript; +import android.util.Log; + +import com.android.gallery3d.filtershow.cache.ImageLoader; +import com.android.gallery3d.filtershow.filters.FilterRepresentation; +import com.android.gallery3d.filtershow.filters.FiltersManager; +import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils; +import com.android.gallery3d.filtershow.imageshow.MasterImage; + +import java.util.Vector; + +public class CachingPipeline implements PipelineInterface { + private static final String LOGTAG = "CachingPipeline"; + private boolean DEBUG = false; + + private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888; + + private static volatile RenderScript sRS = null; + + private FiltersManager mFiltersManager = null; + private volatile Bitmap mOriginalBitmap = null; + private volatile Bitmap mResizedOriginalBitmap = null; + + private FilterEnvironment mEnvironment = new FilterEnvironment(); + private CacheProcessing mCachedProcessing = new CacheProcessing(); + + + private volatile Allocation mOriginalAllocation = null; + private volatile Allocation mFiltersOnlyOriginalAllocation = null; + + protected volatile Allocation mInPixelsAllocation; + protected volatile Allocation mOutPixelsAllocation; + private volatile int mWidth = 0; + private volatile int mHeight = 0; + + private volatile float mPreviewScaleFactor = 1.0f; + private volatile float mHighResPreviewScaleFactor = 1.0f; + private volatile String mName = ""; + + public CachingPipeline(FiltersManager filtersManager, String name) { + mFiltersManager = filtersManager; + mName = name; + } + + public static synchronized RenderScript getRenderScriptContext() { + return sRS; + } + + public static synchronized void createRenderscriptContext(Context context) { + if (sRS != null) { + Log.w(LOGTAG, "A prior RS context exists when calling setRenderScriptContext"); + destroyRenderScriptContext(); + } + sRS = RenderScript.create(context); + } + + public static synchronized void destroyRenderScriptContext() { + if (sRS != null) { + sRS.destroy(); + } + sRS = null; + } + + public void stop() { + mEnvironment.setStop(true); + } + + public synchronized void reset() { + synchronized (CachingPipeline.class) { + if (getRenderScriptContext() == null) { + return; + } + mOriginalBitmap = null; // just a reference to the bitmap in ImageLoader + if (mResizedOriginalBitmap != null) { + mResizedOriginalBitmap.recycle(); + mResizedOriginalBitmap = null; + } + if (mOriginalAllocation != null) { + mOriginalAllocation.destroy(); + mOriginalAllocation = null; + } + if (mFiltersOnlyOriginalAllocation != null) { + mFiltersOnlyOriginalAllocation.destroy(); + mFiltersOnlyOriginalAllocation = null; + } + mPreviewScaleFactor = 1.0f; + mHighResPreviewScaleFactor = 1.0f; + + destroyPixelAllocations(); + } + } + + public Resources getResources() { + return sRS.getApplicationContext().getResources(); + } + + private synchronized void destroyPixelAllocations() { + if (DEBUG) { + Log.v(LOGTAG, "destroyPixelAllocations in " + getName()); + } + if (mInPixelsAllocation != null) { + mInPixelsAllocation.destroy(); + mInPixelsAllocation = null; + } + if (mOutPixelsAllocation != null) { + mOutPixelsAllocation.destroy(); + mOutPixelsAllocation = null; + } + mWidth = 0; + mHeight = 0; + } + + private String getType(RenderingRequest request) { + if (request.getType() == RenderingRequest.ICON_RENDERING) { + return "ICON_RENDERING"; + } + if (request.getType() == RenderingRequest.FILTERS_RENDERING) { + return "FILTERS_RENDERING"; + } + if (request.getType() == RenderingRequest.FULL_RENDERING) { + return "FULL_RENDERING"; + } + if (request.getType() == RenderingRequest.GEOMETRY_RENDERING) { + return "GEOMETRY_RENDERING"; + } + if (request.getType() == RenderingRequest.PARTIAL_RENDERING) { + return "PARTIAL_RENDERING"; + } + if (request.getType() == RenderingRequest.HIGHRES_RENDERING) { + return "HIGHRES_RENDERING"; + } + return "UNKNOWN TYPE!"; + } + + private void setupEnvironment(ImagePreset preset, boolean highResPreview) { + mEnvironment.setPipeline(this); + mEnvironment.setFiltersManager(mFiltersManager); + if (highResPreview) { + mEnvironment.setScaleFactor(mHighResPreviewScaleFactor); + } else { + mEnvironment.setScaleFactor(mPreviewScaleFactor); + } + mEnvironment.setQuality(FilterEnvironment.QUALITY_PREVIEW); + mEnvironment.setImagePreset(preset); + mEnvironment.setStop(false); + } + + public void setOriginal(Bitmap bitmap) { + mOriginalBitmap = bitmap; + Log.v(LOGTAG,"setOriginal, size " + bitmap.getWidth() + " x " + bitmap.getHeight()); + ImagePreset preset = MasterImage.getImage().getPreset(); + setupEnvironment(preset, false); + updateOriginalAllocation(preset); + } + + private synchronized boolean updateOriginalAllocation(ImagePreset preset) { + Bitmap originalBitmap = mOriginalBitmap; + + if (originalBitmap == null) { + return false; + } + + RenderScript RS = getRenderScriptContext(); + + Allocation filtersOnlyOriginalAllocation = mFiltersOnlyOriginalAllocation; + mFiltersOnlyOriginalAllocation = Allocation.createFromBitmap(RS, originalBitmap, + Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); + if (filtersOnlyOriginalAllocation != null) { + filtersOnlyOriginalAllocation.destroy(); + } + + Allocation originalAllocation = mOriginalAllocation; + mResizedOriginalBitmap = preset.applyGeometry(originalBitmap, mEnvironment); + mOriginalAllocation = Allocation.createFromBitmap(RS, mResizedOriginalBitmap, + Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); + if (originalAllocation != null) { + originalAllocation.destroy(); + } + + return true; + } + + public void renderHighres(RenderingRequest request) { + synchronized (CachingPipeline.class) { + if (getRenderScriptContext() == null) { + return; + } + ImagePreset preset = request.getImagePreset(); + setupEnvironment(preset, false); + Bitmap bitmap = MasterImage.getImage().getOriginalBitmapHighres(); + if (bitmap == null) { + return; + } + // TODO: use a cache of bitmaps + bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true); + bitmap = preset.applyGeometry(bitmap, mEnvironment); + + mEnvironment.setQuality(FilterEnvironment.QUALITY_PREVIEW); + Bitmap bmp = preset.apply(bitmap, mEnvironment); + if (!mEnvironment.needsStop()) { + request.setBitmap(bmp); + } + mFiltersManager.freeFilterResources(preset); + } + } + + public synchronized void render(RenderingRequest request) { + synchronized (CachingPipeline.class) { + if (getRenderScriptContext() == null) { + return; + } + if (((request.getType() != RenderingRequest.PARTIAL_RENDERING + && request.getType() != RenderingRequest.HIGHRES_RENDERING) + && request.getBitmap() == null) + || request.getImagePreset() == null) { + return; + } + + if (DEBUG) { + Log.v(LOGTAG, "render image of type " + getType(request)); + } + + Bitmap bitmap = request.getBitmap(); + ImagePreset preset = request.getImagePreset(); + setupEnvironment(preset, + request.getType() != RenderingRequest.HIGHRES_RENDERING); + mFiltersManager.freeFilterResources(preset); + + if (request.getType() == RenderingRequest.PARTIAL_RENDERING) { + MasterImage master = MasterImage.getImage(); + bitmap = ImageLoader.getScaleOneImageForPreset(master.getActivity(), + master.getUri(), request.getBounds(), + request.getDestination()); + if (bitmap == null) { + Log.w(LOGTAG, "could not get bitmap for: " + getType(request)); + return; + } + } + + if (request.getType() == RenderingRequest.HIGHRES_RENDERING) { + bitmap = MasterImage.getImage().getOriginalBitmapHighres(); + if (bitmap != null) { + bitmap = preset.applyGeometry(bitmap, mEnvironment); + } + } + + if (request.getType() == RenderingRequest.FULL_RENDERING + || request.getType() == RenderingRequest.GEOMETRY_RENDERING + || request.getType() == RenderingRequest.FILTERS_RENDERING) { + updateOriginalAllocation(preset); + } + + if (DEBUG) { + Log.v(LOGTAG, "after update, req bitmap (" + bitmap.getWidth() + "x" + bitmap.getHeight() + + " ? resizeOriginal (" + mResizedOriginalBitmap.getWidth() + "x" + + mResizedOriginalBitmap.getHeight()); + } + + if (request.getType() == RenderingRequest.FULL_RENDERING + || request.getType() == RenderingRequest.GEOMETRY_RENDERING) { + mOriginalAllocation.copyTo(bitmap); + } else if (request.getType() == RenderingRequest.FILTERS_RENDERING) { + mFiltersOnlyOriginalAllocation.copyTo(bitmap); + } + + if (request.getType() == RenderingRequest.FULL_RENDERING + || request.getType() == RenderingRequest.FILTERS_RENDERING + || request.getType() == RenderingRequest.ICON_RENDERING + || request.getType() == RenderingRequest.PARTIAL_RENDERING + || request.getType() == RenderingRequest.HIGHRES_RENDERING + || request.getType() == RenderingRequest.STYLE_ICON_RENDERING) { + + if (request.getType() == RenderingRequest.ICON_RENDERING) { + mEnvironment.setQuality(FilterEnvironment.QUALITY_ICON); + } else { + mEnvironment.setQuality(FilterEnvironment.QUALITY_PREVIEW); + } + + Bitmap bmp = preset.apply(bitmap, mEnvironment); + if (!mEnvironment.needsStop()) { + request.setBitmap(bmp); + } + mFiltersManager.freeFilterResources(preset); + } + } + } + + public synchronized void renderImage(ImagePreset preset, Allocation in, Allocation out) { + synchronized (CachingPipeline.class) { + if (getRenderScriptContext() == null) { + return; + } + setupEnvironment(preset, false); + mFiltersManager.freeFilterResources(preset); + preset.applyFilters(-1, -1, in, out, mEnvironment); + boolean copyOut = false; + if (preset.nbFilters() > 0) { + copyOut = true; + } + preset.applyBorder(in, out, copyOut, mEnvironment); + } + } + + public synchronized Bitmap renderFinalImage(Bitmap bitmap, ImagePreset preset) { + synchronized (CachingPipeline.class) { + if (getRenderScriptContext() == null) { + return bitmap; + } + setupEnvironment(preset, false); + mEnvironment.setQuality(FilterEnvironment.QUALITY_FINAL); + mEnvironment.setScaleFactor(1.0f); + mFiltersManager.freeFilterResources(preset); + bitmap = preset.applyGeometry(bitmap, mEnvironment); + bitmap = preset.apply(bitmap, mEnvironment); + return bitmap; + } + } + + public Bitmap renderGeometryIcon(Bitmap bitmap, ImagePreset preset) { + return GeometryMathUtils.applyGeometryRepresentations(preset.getGeometryFilters(), bitmap); + } + + public void compute(SharedBuffer buffer, ImagePreset preset, int type) { + if (getRenderScriptContext() == null) { + return; + } + setupEnvironment(preset, false); + Vector<FilterRepresentation> filters = preset.getFilters(); + Bitmap result = mCachedProcessing.process(mOriginalBitmap, filters, mEnvironment); + buffer.setProducer(result); + } + + public synchronized void computeOld(SharedBuffer buffer, ImagePreset preset, int type) { + synchronized (CachingPipeline.class) { + if (getRenderScriptContext() == null) { + return; + } + if (DEBUG) { + Log.v(LOGTAG, "compute preset " + preset); + preset.showFilters(); + } + + String thread = Thread.currentThread().getName(); + long time = System.currentTimeMillis(); + setupEnvironment(preset, false); + mFiltersManager.freeFilterResources(preset); + + Bitmap resizedOriginalBitmap = mResizedOriginalBitmap; + if (updateOriginalAllocation(preset) || buffer.getProducer() == null) { + resizedOriginalBitmap = mResizedOriginalBitmap; + buffer.setProducer(resizedOriginalBitmap); + mEnvironment.cache(buffer.getProducer()); + } + + Bitmap bitmap = buffer.getProducer().getBitmap(); + long time2 = System.currentTimeMillis(); + + if (bitmap == null || (bitmap.getWidth() != resizedOriginalBitmap.getWidth()) + || (bitmap.getHeight() != resizedOriginalBitmap.getHeight())) { + mEnvironment.cache(buffer.getProducer()); + buffer.setProducer(resizedOriginalBitmap); + bitmap = buffer.getProducer().getBitmap(); + } + mOriginalAllocation.copyTo(bitmap); + + Bitmap tmpbitmap = preset.apply(bitmap, mEnvironment); + if (tmpbitmap != bitmap) { + mEnvironment.cache(buffer.getProducer()); + buffer.setProducer(tmpbitmap); + } + + mFiltersManager.freeFilterResources(preset); + + time = System.currentTimeMillis() - time; + time2 = System.currentTimeMillis() - time2; + if (DEBUG) { + Log.v(LOGTAG, "Applying type " + type + " filters to bitmap " + + bitmap + " (" + bitmap.getWidth() + " x " + bitmap.getHeight() + + ") took " + time + " ms, " + time2 + " ms for the filter, on thread " + thread); + } + } + } + + public boolean needsRepaint() { + SharedBuffer buffer = MasterImage.getImage().getPreviewBuffer(); + return buffer.checkRepaintNeeded(); + } + + public void setPreviewScaleFactor(float previewScaleFactor) { + mPreviewScaleFactor = previewScaleFactor; + } + + public void setHighResPreviewScaleFactor(float highResPreviewScaleFactor) { + mHighResPreviewScaleFactor = highResPreviewScaleFactor; + } + + public synchronized boolean isInitialized() { + return getRenderScriptContext() != null && mOriginalBitmap != null; + } + + public boolean prepareRenderscriptAllocations(Bitmap bitmap) { + RenderScript RS = getRenderScriptContext(); + boolean needsUpdate = false; + if (mOutPixelsAllocation == null || mInPixelsAllocation == null || + bitmap.getWidth() != mWidth || bitmap.getHeight() != mHeight) { + destroyPixelAllocations(); + Bitmap bitmapBuffer = bitmap; + if (bitmap.getConfig() == null || bitmap.getConfig() != BITMAP_CONFIG) { + bitmapBuffer = bitmap.copy(BITMAP_CONFIG, true); + } + mOutPixelsAllocation = Allocation.createFromBitmap(RS, bitmapBuffer, + Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); + mInPixelsAllocation = Allocation.createTyped(RS, + mOutPixelsAllocation.getType()); + needsUpdate = true; + } + if (RS != null) { + mInPixelsAllocation.copyFrom(bitmap); + } + if (bitmap.getWidth() != mWidth + || bitmap.getHeight() != mHeight) { + mWidth = bitmap.getWidth(); + mHeight = bitmap.getHeight(); + needsUpdate = true; + } + if (DEBUG) { + Log.v(LOGTAG, "prepareRenderscriptAllocations: " + needsUpdate + " in " + getName()); + } + return needsUpdate; + } + + public synchronized Allocation getInPixelsAllocation() { + return mInPixelsAllocation; + } + + public synchronized Allocation getOutPixelsAllocation() { + return mOutPixelsAllocation; + } + + public String getName() { + return mName; + } + + public RenderScript getRSContext() { + return CachingPipeline.getRenderScriptContext(); + } +} diff --git a/src/com/android/gallery3d/filtershow/pipeline/FilterEnvironment.java b/src/com/android/gallery3d/filtershow/pipeline/FilterEnvironment.java new file mode 100644 index 000000000..4fac956be --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/FilterEnvironment.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2013 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 com.android.gallery3d.filtershow.pipeline; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.support.v8.renderscript.Allocation; + +import com.android.gallery3d.filtershow.filters.FilterRepresentation; +import com.android.gallery3d.filtershow.filters.FilterUserPresetRepresentation; +import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation.Rotation; +import com.android.gallery3d.filtershow.filters.FiltersManagerInterface; +import com.android.gallery3d.filtershow.filters.ImageFilter; + +import java.lang.ref.WeakReference; +import java.util.HashMap; + +public class FilterEnvironment { + private static final String LOGTAG = "FilterEnvironment"; + private ImagePreset mImagePreset; + private float mScaleFactor; + private int mQuality; + private FiltersManagerInterface mFiltersManager; + private PipelineInterface mPipeline; + private volatile boolean mStop = false; + + public static final int QUALITY_ICON = 0; + public static final int QUALITY_PREVIEW = 1; + public static final int QUALITY_FINAL = 2; + + public synchronized boolean needsStop() { + return mStop; + } + + public synchronized void setStop(boolean stop) { + this.mStop = stop; + } + + private HashMap<Long, WeakReference<Bitmap>> + bitmapCach = new HashMap<Long, WeakReference<Bitmap>>(); + + private HashMap<Integer, Integer> + generalParameters = new HashMap<Integer, Integer>(); + + public void cache(Buffer buffer) { + if (buffer == null) { + return; + } + Bitmap bitmap = buffer.getBitmap(); + if (bitmap == null) { + return; + } + Long key = calcKey(bitmap.getWidth(), bitmap.getHeight()); + bitmapCach.put(key, new WeakReference<Bitmap>(bitmap)); + } + + public Bitmap getBitmap(int w, int h) { + Long key = calcKey(w, h); + WeakReference<Bitmap> ref = bitmapCach.remove(key); + Bitmap bitmap = null; + if (ref != null) { + bitmap = ref.get(); + } + if (bitmap == null) { + bitmap = Bitmap.createBitmap( + w, h, Bitmap.Config.ARGB_8888); + } + return bitmap; + } + + private Long calcKey(long w, long h) { + return (w << 32) | (h << 32); + } + + public void setImagePreset(ImagePreset imagePreset) { + mImagePreset = imagePreset; + } + + public ImagePreset getImagePreset() { + return mImagePreset; + } + + public void setScaleFactor(float scaleFactor) { + mScaleFactor = scaleFactor; + } + + public float getScaleFactor() { + return mScaleFactor; + } + + public void setQuality(int quality) { + mQuality = quality; + } + + public int getQuality() { + return mQuality; + } + + public void setFiltersManager(FiltersManagerInterface filtersManager) { + mFiltersManager = filtersManager; + } + + public FiltersManagerInterface getFiltersManager() { + return mFiltersManager; + } + + public void applyRepresentation(FilterRepresentation representation, + Allocation in, Allocation out) { + ImageFilter filter = mFiltersManager.getFilterForRepresentation(representation); + filter.useRepresentation(representation); + filter.setEnvironment(this); + if (filter.supportsAllocationInput()) { + filter.apply(in, out); + } + filter.setGeneralParameters(); + filter.setEnvironment(null); + } + + public Bitmap applyRepresentation(FilterRepresentation representation, Bitmap bitmap) { + if (representation instanceof FilterUserPresetRepresentation) { + // we allow instances of FilterUserPresetRepresentation in a preset only to know if one + // has been applied (so we can show this in the UI). But as all the filters in them are + // applied directly they do not themselves need to do any kind of filtering. + return bitmap; + } + ImageFilter filter = mFiltersManager.getFilterForRepresentation(representation); + filter.useRepresentation(representation); + filter.setEnvironment(this); + Bitmap ret = filter.apply(bitmap, mScaleFactor, mQuality); + filter.setGeneralParameters(); + filter.setEnvironment(null); + return ret; + } + + public PipelineInterface getPipeline() { + return mPipeline; + } + + public void setPipeline(PipelineInterface cachingPipeline) { + mPipeline = cachingPipeline; + } + + public synchronized void clearGeneralParameters() { + generalParameters = null; + } + + public synchronized Integer getGeneralParameter(int id) { + if (generalParameters == null || !generalParameters.containsKey(id)) { + return null; + } + return generalParameters.get(id); + } + + public synchronized void setGeneralParameter(int id, int value) { + if (generalParameters == null) { + generalParameters = new HashMap<Integer, Integer>(); + } + + generalParameters.put(id, value); + } + +} diff --git a/src/com/android/gallery3d/filtershow/pipeline/HighresRenderingRequestTask.java b/src/com/android/gallery3d/filtershow/pipeline/HighresRenderingRequestTask.java new file mode 100644 index 000000000..5a0eb4d45 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/HighresRenderingRequestTask.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2013 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 com.android.gallery3d.filtershow.pipeline; + +import android.graphics.Bitmap; +import com.android.gallery3d.filtershow.filters.FiltersManager; + +public class HighresRenderingRequestTask extends ProcessingTask { + + private CachingPipeline mHighresPreviewPipeline = null; + private boolean mPipelineIsOn = false; + + public void setHighresPreviewScaleFactor(float highResPreviewScale) { + mHighresPreviewPipeline.setHighResPreviewScaleFactor(highResPreviewScale); + } + + public void setPreviewScaleFactor(float previewScale) { + mHighresPreviewPipeline.setPreviewScaleFactor(previewScale); + } + + static class Render implements Request { + RenderingRequest request; + } + + static class RenderResult implements Result { + RenderingRequest request; + } + + public HighresRenderingRequestTask() { + mHighresPreviewPipeline = new CachingPipeline( + FiltersManager.getHighresManager(), "Highres"); + } + + public void setOriginal(Bitmap bitmap) { + mHighresPreviewPipeline.setOriginal(bitmap); + } + + public void setOriginalBitmapHighres(Bitmap originalHires) { + mPipelineIsOn = true; + } + + public void stop() { + mHighresPreviewPipeline.stop(); + } + + public void postRenderingRequest(RenderingRequest request) { + if (!mPipelineIsOn) { + return; + } + Render render = new Render(); + render.request = request; + postRequest(render); + } + + @Override + public Result doInBackground(Request message) { + RenderingRequest request = ((Render) message).request; + RenderResult result = null; + mHighresPreviewPipeline.renderHighres(request); + result = new RenderResult(); + result.request = request; + return result; + } + + @Override + public void onResult(Result message) { + if (message == null) { + return; + } + RenderingRequest request = ((RenderResult) message).request; + request.markAvailable(); + } + + @Override + public boolean isDelayedTask() { return true; } +} diff --git a/src/com/android/gallery3d/filtershow/pipeline/ImagePreset.java b/src/com/android/gallery3d/filtershow/pipeline/ImagePreset.java new file mode 100644 index 000000000..d34216ad6 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/ImagePreset.java @@ -0,0 +1,694 @@ +/* + * Copyright (C) 2012 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 com.android.gallery3d.filtershow.pipeline; + +import android.graphics.Bitmap; +import android.graphics.Rect; +import android.support.v8.renderscript.Allocation; +import android.util.JsonReader; +import android.util.JsonWriter; +import android.util.Log; + +import com.android.gallery3d.R; +import com.android.gallery3d.filtershow.cache.ImageLoader; +import com.android.gallery3d.filtershow.filters.BaseFiltersManager; +import com.android.gallery3d.filtershow.filters.FilterCropRepresentation; +import com.android.gallery3d.filtershow.filters.FilterFxRepresentation; +import com.android.gallery3d.filtershow.filters.FilterImageBorderRepresentation; +import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation; +import com.android.gallery3d.filtershow.filters.FilterRepresentation; +import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation; +import com.android.gallery3d.filtershow.filters.FilterStraightenRepresentation; +import com.android.gallery3d.filtershow.filters.FilterUserPresetRepresentation; +import com.android.gallery3d.filtershow.filters.FiltersManager; +import com.android.gallery3d.filtershow.filters.ImageFilter; +import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils; +import com.android.gallery3d.filtershow.imageshow.MasterImage; +import com.android.gallery3d.filtershow.state.State; +import com.android.gallery3d.filtershow.state.StateAdapter; +import com.android.gallery3d.util.UsageStatistics; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Vector; + +public class ImagePreset { + + private static final String LOGTAG = "ImagePreset"; + + private Vector<FilterRepresentation> mFilters = new Vector<FilterRepresentation>(); + + private boolean mDoApplyGeometry = true; + private boolean mDoApplyFilters = true; + + private boolean mPartialRendering = false; + private Rect mPartialRenderingBounds; + private static final boolean DEBUG = false; + + public ImagePreset() { + } + + public ImagePreset(ImagePreset source) { + for (int i = 0; i < source.mFilters.size(); i++) { + FilterRepresentation sourceRepresentation = source.mFilters.elementAt(i); + mFilters.add(sourceRepresentation.copy()); + } + } + + public Vector<FilterRepresentation> getFilters() { + return mFilters; + } + + public FilterRepresentation getFilterRepresentation(int position) { + FilterRepresentation representation = null; + + representation = mFilters.elementAt(position).copy(); + + return representation; + } + + private static boolean sameSerializationName(String a, String b) { + if (a != null && b != null) { + return a.equals(b); + } else { + return a == null && b == null; + } + } + + public static boolean sameSerializationName(FilterRepresentation a, FilterRepresentation b) { + if (a == null || b == null) { + return false; + } + return sameSerializationName(a.getSerializationName(), b.getSerializationName()); + } + + public int getPositionForRepresentation(FilterRepresentation representation) { + for (int i = 0; i < mFilters.size(); i++) { + if (sameSerializationName(mFilters.elementAt(i), representation)) { + return i; + } + } + return -1; + } + + private FilterRepresentation getFilterRepresentationForType(int type) { + for (int i = 0; i < mFilters.size(); i++) { + if (mFilters.elementAt(i).getFilterType() == type) { + return mFilters.elementAt(i); + } + } + return null; + } + + public int getPositionForType(int type) { + for (int i = 0; i < mFilters.size(); i++) { + if (mFilters.elementAt(i).getFilterType() == type) { + return i; + } + } + return -1; + } + + public FilterRepresentation getFilterRepresentationCopyFrom( + FilterRepresentation filterRepresentation) { + // TODO: add concept of position in the filters (to allow multiple instances) + if (filterRepresentation == null) { + return null; + } + int position = getPositionForRepresentation(filterRepresentation); + if (position == -1) { + return null; + } + FilterRepresentation representation = mFilters.elementAt(position); + if (representation != null) { + representation = representation.copy(); + } + return representation; + } + + public void updateFilterRepresentations(Collection<FilterRepresentation> reps) { + for (FilterRepresentation r : reps) { + updateOrAddFilterRepresentation(r); + } + } + + public void updateOrAddFilterRepresentation(FilterRepresentation rep) { + int pos = getPositionForRepresentation(rep); + if (pos != -1) { + mFilters.elementAt(pos).useParametersFrom(rep); + } else { + addFilter(rep.copy()); + } + } + + public void setDoApplyGeometry(boolean value) { + mDoApplyGeometry = value; + } + + public void setDoApplyFilters(boolean value) { + mDoApplyFilters = value; + } + + public boolean getDoApplyFilters() { + return mDoApplyFilters; + } + + public boolean hasModifications() { + for (int i = 0; i < mFilters.size(); i++) { + FilterRepresentation filter = mFilters.elementAt(i); + if (!filter.isNil()) { + return true; + } + } + return false; + } + + public boolean isPanoramaSafe() { + for (FilterRepresentation representation : mFilters) { + if (representation.getFilterType() == FilterRepresentation.TYPE_GEOMETRY + && !representation.isNil()) { + return false; + } + if (representation.getFilterType() == FilterRepresentation.TYPE_BORDER + && !representation.isNil()) { + return false; + } + if (representation.getFilterType() == FilterRepresentation.TYPE_VIGNETTE + && !representation.isNil()) { + return false; + } + if (representation.getFilterType() == FilterRepresentation.TYPE_TINYPLANET + && !representation.isNil()) { + return false; + } + } + return true; + } + + public boolean same(ImagePreset preset) { + if (preset == null) { + return false; + } + + if (preset.mFilters.size() != mFilters.size()) { + return false; + } + + if (mDoApplyGeometry != preset.mDoApplyGeometry) { + return false; + } + + if (mDoApplyFilters != preset.mDoApplyFilters) { + if (mFilters.size() > 0 || preset.mFilters.size() > 0) { + return false; + } + } + + if (mDoApplyFilters && preset.mDoApplyFilters) { + for (int i = 0; i < preset.mFilters.size(); i++) { + FilterRepresentation a = preset.mFilters.elementAt(i); + FilterRepresentation b = mFilters.elementAt(i); + + if (!a.same(b)) { + return false; + } + } + } + + return true; + } + + public int similarUpTo(ImagePreset preset) { + for (int i = 0; i < preset.mFilters.size(); i++) { + FilterRepresentation a = preset.mFilters.elementAt(i); + if (i < mFilters.size()) { + FilterRepresentation b = mFilters.elementAt(i); + if (!a.same(b)) { + return i; + } + if (!a.equals(b)) { + return i; + } + } else { + return i; + } + } + return preset.mFilters.size(); + } + + public void showFilters() { + Log.v(LOGTAG, "\\\\\\ showFilters -- " + mFilters.size() + " filters"); + int n = 0; + for (FilterRepresentation representation : mFilters) { + Log.v(LOGTAG, " filter " + n + " : " + representation.toString()); + n++; + } + Log.v(LOGTAG, "/// showFilters -- " + mFilters.size() + " filters"); + } + + public FilterRepresentation getLastRepresentation() { + if (mFilters.size() > 0) { + return mFilters.lastElement(); + } + return null; + } + + public void removeFilter(FilterRepresentation filterRepresentation) { + if (filterRepresentation.getFilterType() == FilterRepresentation.TYPE_BORDER) { + for (int i = 0; i < mFilters.size(); i++) { + if (mFilters.elementAt(i).getFilterType() + == filterRepresentation.getFilterType()) { + mFilters.remove(i); + break; + } + } + } else { + for (int i = 0; i < mFilters.size(); i++) { + if (sameSerializationName(mFilters.elementAt(i), filterRepresentation)) { + mFilters.remove(i); + break; + } + } + } + } + + // If the filter is an "None" effect or border, then just don't add this filter. + public void addFilter(FilterRepresentation representation) { + if (representation instanceof FilterUserPresetRepresentation) { + ImagePreset preset = ((FilterUserPresetRepresentation) representation).getImagePreset(); + // user preset replace everything but geometry + mFilters.clear(); + for (int i = 0; i < preset.nbFilters(); i++) { + addFilter(preset.getFilterRepresentation(i)); + } + mFilters.add(representation); + } else if (representation.getFilterType() == FilterRepresentation.TYPE_GEOMETRY) { + // Add geometry filter, removing duplicates and do-nothing operations. + for (int i = 0; i < mFilters.size(); i++) { + if (sameSerializationName(representation, mFilters.elementAt(i))) { + mFilters.remove(i); + } + } + if (!representation.isNil()) { + mFilters.add(representation); + } + } else if (representation.getFilterType() == FilterRepresentation.TYPE_BORDER) { + removeFilter(representation); + if (!isNoneBorderFilter(representation)) { + mFilters.add(representation); + } + } else if (representation.getFilterType() == FilterRepresentation.TYPE_FX) { + boolean found = false; + for (int i = 0; i < mFilters.size(); i++) { + FilterRepresentation current = mFilters.elementAt(i); + int type = current.getFilterType(); + if (found) { + if (type != FilterRepresentation.TYPE_VIGNETTE) { + mFilters.remove(i); + continue; + } + } + if (type == FilterRepresentation.TYPE_FX) { + if (current instanceof FilterUserPresetRepresentation) { + ImagePreset preset = ((FilterUserPresetRepresentation) current) + .getImagePreset(); + // If we had an existing user preset, let's remove all the presets that + // were added by it + for (int j = 0; j < preset.nbFilters(); j++) { + FilterRepresentation rep = preset.getFilterRepresentation(j); + int pos = getPositionForRepresentation(rep); + if (pos != -1) { + mFilters.remove(pos); + } + } + int pos = getPositionForRepresentation(current); + if (pos != -1) { + mFilters.remove(pos); + } else { + pos = 0; + } + if (!isNoneFxFilter(representation)) { + mFilters.add(pos, representation); + } + + } else { + mFilters.remove(i); + if (!isNoneFxFilter(representation)) { + mFilters.add(i, representation); + } + } + found = true; + } + } + if (!found) { + if (!isNoneFxFilter(representation)) { + mFilters.add(representation); + } + } + } else { + mFilters.add(representation); + } + } + + private boolean isNoneBorderFilter(FilterRepresentation representation) { + return representation instanceof FilterImageBorderRepresentation && + ((FilterImageBorderRepresentation) representation).getDrawableResource() == 0; + } + + private boolean isNoneFxFilter(FilterRepresentation representation) { + return representation instanceof FilterFxRepresentation && + ((FilterFxRepresentation) representation).getNameResource() == R.string.none; + } + + public FilterRepresentation getRepresentation(FilterRepresentation filterRepresentation) { + for (int i = 0; i < mFilters.size(); i++) { + FilterRepresentation representation = mFilters.elementAt(i); + if (sameSerializationName(representation, filterRepresentation)) { + return representation; + } + } + return null; + } + + public Bitmap apply(Bitmap original, FilterEnvironment environment) { + Bitmap bitmap = original; + bitmap = applyFilters(bitmap, -1, -1, environment); + return applyBorder(bitmap, environment); + } + + public Collection<FilterRepresentation> getGeometryFilters() { + ArrayList<FilterRepresentation> geometry = new ArrayList<FilterRepresentation>(); + for (FilterRepresentation r : mFilters) { + if (r.getFilterType() == FilterRepresentation.TYPE_GEOMETRY) { + geometry.add(r); + } + } + return geometry; + } + + public FilterRepresentation getFilterWithSerializationName(String serializationName) { + for (FilterRepresentation r : mFilters) { + if (r != null) { + if (sameSerializationName(r.getSerializationName(), serializationName)) { + return r.copy(); + } + } + } + return null; + } + + public Bitmap applyGeometry(Bitmap bitmap, FilterEnvironment environment) { + // Apply any transform -- 90 rotate, flip, straighten, crop + // Returns a new bitmap. + if (mDoApplyGeometry) { + bitmap = GeometryMathUtils.applyGeometryRepresentations(getGeometryFilters(), bitmap); + } + return bitmap; + } + + public Bitmap applyBorder(Bitmap bitmap, FilterEnvironment environment) { + // get the border from the list of filters. + FilterRepresentation border = getFilterRepresentationForType( + FilterRepresentation.TYPE_BORDER); + if (border != null && mDoApplyGeometry) { + bitmap = environment.applyRepresentation(border, bitmap); + if (environment.getQuality() == FilterEnvironment.QUALITY_FINAL) { + UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR, + "SaveBorder", border.getSerializationName(), 1); + } + } + return bitmap; + } + + public int nbFilters() { + return mFilters.size(); + } + + public Bitmap applyFilters(Bitmap bitmap, int from, int to, FilterEnvironment environment) { + if (mDoApplyFilters) { + if (from < 0) { + from = 0; + } + if (to == -1) { + to = mFilters.size(); + } + if (environment.getQuality() == FilterEnvironment.QUALITY_FINAL) { + UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR, + "SaveFilters", "Total", to - from + 1); + } + for (int i = from; i < to; i++) { + FilterRepresentation representation = mFilters.elementAt(i); + if (representation.getFilterType() == FilterRepresentation.TYPE_GEOMETRY) { + // skip the geometry as it's already applied. + continue; + } + if (representation.getFilterType() == FilterRepresentation.TYPE_BORDER) { + // for now, let's skip the border as it will be applied in + // applyBorder() + // TODO: might be worth getting rid of applyBorder. + continue; + } + bitmap = environment.applyRepresentation(representation, bitmap); + if (environment.getQuality() == FilterEnvironment.QUALITY_FINAL) { + UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR, + "SaveFilter", representation.getSerializationName(), 1); + } + if (environment.needsStop()) { + return bitmap; + } + } + } + + return bitmap; + } + + public void applyBorder(Allocation in, Allocation out, + boolean copyOut, FilterEnvironment environment) { + FilterRepresentation border = getFilterRepresentationForType( + FilterRepresentation.TYPE_BORDER); + if (border != null && mDoApplyGeometry) { + // TODO: should keep the bitmap around + Allocation bitmapIn = in; + if (copyOut) { + bitmapIn = Allocation.createTyped( + CachingPipeline.getRenderScriptContext(), in.getType()); + bitmapIn.copyFrom(out); + } + environment.applyRepresentation(border, bitmapIn, out); + } + } + + public void applyFilters(int from, int to, Allocation in, Allocation out, + FilterEnvironment environment) { + if (mDoApplyFilters) { + if (from < 0) { + from = 0; + } + if (to == -1) { + to = mFilters.size(); + } + for (int i = from; i < to; i++) { + FilterRepresentation representation = mFilters.elementAt(i); + if (representation.getFilterType() == FilterRepresentation.TYPE_GEOMETRY + || representation.getFilterType() == FilterRepresentation.TYPE_BORDER) { + continue; + } + if (i > from) { + in.copyFrom(out); + } + environment.applyRepresentation(representation, in, out); + } + } + } + + public boolean canDoPartialRendering() { + if (MasterImage.getImage().getZoomOrientation() != ImageLoader.ORI_NORMAL) { + return false; + } + for (int i = 0; i < mFilters.size(); i++) { + FilterRepresentation representation = mFilters.elementAt(i); + if (representation.getFilterType() == FilterRepresentation.TYPE_GEOMETRY + && !representation.isNil()) { + return false; + } + if (!representation.supportsPartialRendering()) { + return false; + } + } + return true; + } + + public void fillImageStateAdapter(StateAdapter imageStateAdapter) { + if (imageStateAdapter == null) { + return; + } + Vector<State> states = new Vector<State>(); + for (FilterRepresentation filter : mFilters) { + if (filter.getFilterType() == FilterRepresentation.TYPE_GEOMETRY) { + // TODO: supports Geometry representations in the state panel. + continue; + } + if (filter instanceof FilterUserPresetRepresentation) { + // do not show the user preset itself in the state panel + continue; + } + State state = new State(filter.getName()); + state.setFilterRepresentation(filter); + states.add(state); + } + imageStateAdapter.fill(states); + } + + public void setPartialRendering(boolean partialRendering, Rect bounds) { + mPartialRendering = partialRendering; + mPartialRenderingBounds = bounds; + } + + public boolean isPartialRendering() { + return mPartialRendering; + } + + public Rect getPartialRenderingBounds() { + return mPartialRenderingBounds; + } + + public Vector<ImageFilter> getUsedFilters(BaseFiltersManager filtersManager) { + Vector<ImageFilter> usedFilters = new Vector<ImageFilter>(); + for (int i = 0; i < mFilters.size(); i++) { + FilterRepresentation representation = mFilters.elementAt(i); + ImageFilter filter = filtersManager.getFilterForRepresentation(representation); + usedFilters.add(filter); + } + return usedFilters; + } + + public String getJsonString(String name) { + StringWriter swriter = new StringWriter(); + try { + JsonWriter writer = new JsonWriter(swriter); + writeJson(writer, name); + writer.close(); + } catch (IOException e) { + return null; + } + return swriter.toString(); + } + + public void writeJson(JsonWriter writer, String name) { + int numFilters = mFilters.size(); + try { + writer.beginObject(); + for (int i = 0; i < numFilters; i++) { + FilterRepresentation filter = mFilters.get(i); + if (filter instanceof FilterUserPresetRepresentation) { + continue; + } + String sname = filter.getSerializationName(); + if (DEBUG) { + Log.v(LOGTAG, "Serialization: " + sname); + if (sname == null) { + Log.v(LOGTAG, "Serialization name null for filter: " + filter); + } + } + writer.name(sname); + filter.serializeRepresentation(writer); + } + writer.endObject(); + + } catch (IOException e) { + Log.e(LOGTAG,"Error encoding JASON",e); + } + } + + /** + * populates preset from JSON string + * + * @param filterString a JSON string + * @return true on success if false ImagePreset is undefined + */ + public boolean readJsonFromString(String filterString) { + if (DEBUG) { + Log.v(LOGTAG, "reading preset: \"" + filterString + "\""); + } + StringReader sreader = new StringReader(filterString); + try { + JsonReader reader = new JsonReader(sreader); + boolean ok = readJson(reader); + if (!ok) { + reader.close(); + return false; + } + reader.close(); + } catch (Exception e) { + Log.e(LOGTAG, "parsing the filter parameters:", e); + return false; + } + return true; + } + + /** + * populates preset from JSON stream + * + * @param sreader a JSON string + * @return true on success if false ImagePreset is undefined + */ + public boolean readJson(JsonReader sreader) throws IOException { + sreader.beginObject(); + + while (sreader.hasNext()) { + String name = sreader.nextName(); + FilterRepresentation filter = creatFilterFromName(name); + if (filter == null) { + Log.w(LOGTAG, "UNKNOWN FILTER! " + name); + return false; + } + filter.deSerializeRepresentation(sreader); + addFilter(filter); + } + sreader.endObject(); + return true; + } + + FilterRepresentation creatFilterFromName(String name) { + if (FilterRotateRepresentation.SERIALIZATION_NAME.equals(name)) { + return new FilterRotateRepresentation(); + } else if (FilterMirrorRepresentation.SERIALIZATION_NAME.equals(name)) { + return new FilterMirrorRepresentation(); + } else if (FilterStraightenRepresentation.SERIALIZATION_NAME.equals(name)) { + return new FilterStraightenRepresentation(); + } else if (FilterCropRepresentation.SERIALIZATION_NAME.equals(name)) { + return new FilterCropRepresentation(); + } + FiltersManager filtersManager = FiltersManager.getManager(); + return filtersManager.createFilterFromName(name); + } + + public void updateWith(ImagePreset preset) { + if (preset.mFilters.size() != mFilters.size()) { + Log.e(LOGTAG, "Updating a preset with an incompatible one"); + return; + } + for (int i = 0; i < mFilters.size(); i++) { + FilterRepresentation destRepresentation = mFilters.elementAt(i); + FilterRepresentation sourceRepresentation = preset.mFilters.elementAt(i); + destRepresentation.useParametersFrom(sourceRepresentation); + } + } +} diff --git a/src/com/android/gallery3d/filtershow/pipeline/ImageSavingTask.java b/src/com/android/gallery3d/filtershow/pipeline/ImageSavingTask.java new file mode 100644 index 000000000..b760edd5a --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/ImageSavingTask.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2013 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 com.android.gallery3d.filtershow.pipeline; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.net.Uri; +import com.android.gallery3d.filtershow.cache.ImageLoader; +import com.android.gallery3d.filtershow.filters.FiltersManager; +import com.android.gallery3d.filtershow.tools.SaveImage; + +import java.io.File; + +public class ImageSavingTask extends ProcessingTask { + private ProcessingService mProcessingService; + + static class SaveRequest implements Request { + Uri sourceUri; + Uri selectedUri; + File destinationFile; + ImagePreset preset; + boolean flatten; + int quality; + } + + static class UpdateBitmap implements Update { + Bitmap bitmap; + } + + static class UpdateProgress implements Update { + int max; + int current; + } + + static class URIResult implements Result { + Uri uri; + } + + public ImageSavingTask(ProcessingService service) { + mProcessingService = service; + } + + public void saveImage(Uri sourceUri, Uri selectedUri, + File destinationFile, ImagePreset preset, boolean flatten, int quality) { + SaveRequest request = new SaveRequest(); + request.sourceUri = sourceUri; + request.selectedUri = selectedUri; + request.destinationFile = destinationFile; + request.preset = preset; + request.flatten = flatten; + request.quality = quality; + postRequest(request); + } + + public Result doInBackground(Request message) { + SaveRequest request = (SaveRequest) message; + Uri sourceUri = request.sourceUri; + Uri selectedUri = request.selectedUri; + File destinationFile = request.destinationFile; + ImagePreset preset = request.preset; + boolean flatten = request.flatten; + // We create a small bitmap showing the result that we can + // give to the notification + UpdateBitmap updateBitmap = new UpdateBitmap(); + updateBitmap.bitmap = createNotificationBitmap(sourceUri, preset); + postUpdate(updateBitmap); + SaveImage saveImage = new SaveImage(mProcessingService, sourceUri, + selectedUri, destinationFile, + new SaveImage.Callback() { + @Override + public void onProgress(int max, int current) { + UpdateProgress updateProgress = new UpdateProgress(); + updateProgress.max = max; + updateProgress.current = current; + postUpdate(updateProgress); + } + }); + Uri uri = saveImage.processAndSaveImage(preset, !flatten, request.quality); + URIResult result = new URIResult(); + result.uri = uri; + return result; + } + + @Override + public void onResult(Result message) { + URIResult result = (URIResult) message; + mProcessingService.completeSaveImage(result.uri); + } + + @Override + public void onUpdate(Update message) { + if (message instanceof UpdateBitmap) { + Bitmap bitmap = ((UpdateBitmap) message).bitmap; + mProcessingService.updateNotificationWithBitmap(bitmap); + } + if (message instanceof UpdateProgress) { + UpdateProgress progress = (UpdateProgress) message; + mProcessingService.updateProgress(progress.max, progress.current); + } + } + + private Bitmap createNotificationBitmap(Uri sourceUri, ImagePreset preset) { + int notificationBitmapSize = Resources.getSystem().getDimensionPixelSize( + android.R.dimen.notification_large_icon_width); + Bitmap bitmap = ImageLoader.loadConstrainedBitmap(sourceUri, getContext(), + notificationBitmapSize, null, true); + CachingPipeline pipeline = new CachingPipeline(FiltersManager.getManager(), "Thumb"); + return pipeline.renderFinalImage(bitmap, preset); + } + +} diff --git a/src/com/android/gallery3d/filtershow/pipeline/PipelineInterface.java b/src/com/android/gallery3d/filtershow/pipeline/PipelineInterface.java new file mode 100644 index 000000000..d53768c95 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/PipelineInterface.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2013 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 com.android.gallery3d.filtershow.pipeline; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.support.v8.renderscript.Allocation; +import android.support.v8.renderscript.RenderScript; + +public interface PipelineInterface { + public String getName(); + public Resources getResources(); + public Allocation getInPixelsAllocation(); + public Allocation getOutPixelsAllocation(); + public boolean prepareRenderscriptAllocations(Bitmap bitmap); + public RenderScript getRSContext(); +} diff --git a/src/com/android/gallery3d/filtershow/pipeline/ProcessingService.java b/src/com/android/gallery3d/filtershow/pipeline/ProcessingService.java new file mode 100644 index 000000000..d0504d11f --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/ProcessingService.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2013 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 com.android.gallery3d.filtershow.pipeline; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Binder; +import android.os.IBinder; +import android.util.Log; +import com.android.gallery3d.R; +import com.android.gallery3d.filtershow.FilterShowActivity; +import com.android.gallery3d.filtershow.filters.FiltersManager; +import com.android.gallery3d.filtershow.filters.ImageFilter; +import com.android.gallery3d.filtershow.imageshow.MasterImage; +import com.android.gallery3d.filtershow.tools.SaveImage; + +import java.io.File; + +public class ProcessingService extends Service { + private static final String LOGTAG = "ProcessingService"; + private static final boolean SHOW_IMAGE = false; + private int mNotificationId; + private NotificationManager mNotifyMgr = null; + private Notification.Builder mBuilder = null; + + private static final String PRESET = "preset"; + private static final String QUALITY = "quality"; + private static final String SOURCE_URI = "sourceUri"; + private static final String SELECTED_URI = "selectedUri"; + private static final String DESTINATION_FILE = "destinationFile"; + private static final String SAVING = "saving"; + private static final String FLATTEN = "flatten"; + + private ProcessingTaskController mProcessingTaskController; + private ImageSavingTask mImageSavingTask; + private UpdatePreviewTask mUpdatePreviewTask; + private HighresRenderingRequestTask mHighresRenderingRequestTask; + private RenderingRequestTask mRenderingRequestTask; + + private final IBinder mBinder = new LocalBinder(); + private FilterShowActivity mFiltershowActivity; + + private boolean mSaving = false; + private boolean mNeedsAlive = false; + + public void setFiltershowActivity(FilterShowActivity filtershowActivity) { + mFiltershowActivity = filtershowActivity; + } + + public void setOriginalBitmap(Bitmap originalBitmap) { + if (mUpdatePreviewTask == null) { + return; + } + mUpdatePreviewTask.setOriginal(originalBitmap); + mHighresRenderingRequestTask.setOriginal(originalBitmap); + mRenderingRequestTask.setOriginal(originalBitmap); + } + + public void updatePreviewBuffer() { + mHighresRenderingRequestTask.stop(); + mUpdatePreviewTask.updatePreview(); + } + + public void postRenderingRequest(RenderingRequest request) { + mRenderingRequestTask.postRenderingRequest(request); + } + + public void postHighresRenderingRequest(ImagePreset preset, float scaleFactor, + RenderingRequestCaller caller) { + RenderingRequest request = new RenderingRequest(); + // TODO: use the triple buffer preset as UpdatePreviewTask does instead of creating a copy + ImagePreset passedPreset = new ImagePreset(preset); + request.setOriginalImagePreset(preset); + request.setScaleFactor(scaleFactor); + request.setImagePreset(passedPreset); + request.setType(RenderingRequest.HIGHRES_RENDERING); + request.setCaller(caller); + mHighresRenderingRequestTask.postRenderingRequest(request); + } + + public void setHighresPreviewScaleFactor(float highResPreviewScale) { + mHighresRenderingRequestTask.setHighresPreviewScaleFactor(highResPreviewScale); + } + + public void setPreviewScaleFactor(float previewScale) { + mHighresRenderingRequestTask.setPreviewScaleFactor(previewScale); + mRenderingRequestTask.setPreviewScaleFactor(previewScale); + } + + public void setOriginalBitmapHighres(Bitmap originalHires) { + mHighresRenderingRequestTask.setOriginalBitmapHighres(originalHires); + } + + public class LocalBinder extends Binder { + public ProcessingService getService() { + return ProcessingService.this; + } + } + + public static Intent getSaveIntent(Context context, ImagePreset preset, File destination, + Uri selectedImageUri, Uri sourceImageUri, boolean doFlatten, int quality) { + Intent processIntent = new Intent(context, ProcessingService.class); + processIntent.putExtra(ProcessingService.SOURCE_URI, + sourceImageUri.toString()); + processIntent.putExtra(ProcessingService.SELECTED_URI, + selectedImageUri.toString()); + processIntent.putExtra(ProcessingService.QUALITY, quality); + if (destination != null) { + processIntent.putExtra(ProcessingService.DESTINATION_FILE, destination.toString()); + } + processIntent.putExtra(ProcessingService.PRESET, + preset.getJsonString(context.getString(R.string.saved))); + processIntent.putExtra(ProcessingService.SAVING, true); + if (doFlatten) { + processIntent.putExtra(ProcessingService.FLATTEN, true); + } + return processIntent; + } + + + @Override + public void onCreate() { + mProcessingTaskController = new ProcessingTaskController(this); + mImageSavingTask = new ImageSavingTask(this); + mUpdatePreviewTask = new UpdatePreviewTask(); + mHighresRenderingRequestTask = new HighresRenderingRequestTask(); + mRenderingRequestTask = new RenderingRequestTask(); + mProcessingTaskController.add(mImageSavingTask); + mProcessingTaskController.add(mUpdatePreviewTask); + mProcessingTaskController.add(mHighresRenderingRequestTask); + mProcessingTaskController.add(mRenderingRequestTask); + setupPipeline(); + } + + @Override + public void onDestroy() { + tearDownPipeline(); + mProcessingTaskController.quit(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + mNeedsAlive = true; + if (intent != null && intent.getBooleanExtra(SAVING, false)) { + // we save using an intent to keep the service around after the + // activity has been destroyed. + String presetJson = intent.getStringExtra(PRESET); + String source = intent.getStringExtra(SOURCE_URI); + String selected = intent.getStringExtra(SELECTED_URI); + String destination = intent.getStringExtra(DESTINATION_FILE); + int quality = intent.getIntExtra(QUALITY, 100); + boolean flatten = intent.getBooleanExtra(FLATTEN, false); + Uri sourceUri = Uri.parse(source); + Uri selectedUri = null; + if (selected != null) { + selectedUri = Uri.parse(selected); + } + File destinationFile = null; + if (destination != null) { + destinationFile = new File(destination); + } + ImagePreset preset = new ImagePreset(); + preset.readJsonFromString(presetJson); + mNeedsAlive = false; + mSaving = true; + handleSaveRequest(sourceUri, selectedUri, destinationFile, preset, flatten, quality); + } + return START_REDELIVER_INTENT; + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + public void onStart() { + mNeedsAlive = true; + if (!mSaving && mFiltershowActivity != null) { + mFiltershowActivity.updateUIAfterServiceStarted(); + } + } + + public void handleSaveRequest(Uri sourceUri, Uri selectedUri, + File destinationFile, ImagePreset preset, boolean flatten, int quality) { + mNotifyMgr = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + + mNotificationId++; + + mBuilder = + new Notification.Builder(this) + .setSmallIcon(R.drawable.filtershow_button_fx) + .setContentTitle(getString(R.string.filtershow_notification_label)) + .setContentText(getString(R.string.filtershow_notification_message)); + + startForeground(mNotificationId, mBuilder.build()); + + updateProgress(SaveImage.MAX_PROCESSING_STEPS, 0); + + // Process the image + + mImageSavingTask.saveImage(sourceUri, selectedUri, destinationFile, + preset, flatten, quality); + } + + public void updateNotificationWithBitmap(Bitmap bitmap) { + mBuilder.setLargeIcon(bitmap); + mNotifyMgr.notify(mNotificationId, mBuilder.build()); + } + + public void updateProgress(int max, int current) { + mBuilder.setProgress(max, current, false); + mNotifyMgr.notify(mNotificationId, mBuilder.build()); + } + + public void completeSaveImage(Uri result) { + if (SHOW_IMAGE) { + // TODO: we should update the existing image in Gallery instead + Intent viewImage = new Intent(Intent.ACTION_VIEW, result); + viewImage.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(viewImage); + } + stopForeground(true); + stopSelf(); + if (mNeedsAlive) { + // If the app has been restarted while we were saving... + mFiltershowActivity.updateUIAfterServiceStarted(); + } else if (mFiltershowActivity.isSimpleEditAction()) { + // terminate now + mFiltershowActivity.completeSaveImage(result); + } + } + + private void setupPipeline() { + Resources res = getResources(); + FiltersManager.setResources(res); + CachingPipeline.createRenderscriptContext(this); + + FiltersManager filtersManager = FiltersManager.getManager(); + filtersManager.addLooks(this); + filtersManager.addBorders(this); + filtersManager.addTools(this); + filtersManager.addEffects(); + + FiltersManager highresFiltersManager = FiltersManager.getHighresManager(); + highresFiltersManager.addLooks(this); + highresFiltersManager.addBorders(this); + highresFiltersManager.addTools(this); + highresFiltersManager.addEffects(); + } + + private void tearDownPipeline() { + ImageFilter.resetStatics(); + FiltersManager.getPreviewManager().freeRSFilterScripts(); + FiltersManager.getManager().freeRSFilterScripts(); + FiltersManager.getHighresManager().freeRSFilterScripts(); + FiltersManager.reset(); + CachingPipeline.destroyRenderScriptContext(); + } + + static { + System.loadLibrary("jni_filtershow_filters"); + } +} diff --git a/src/com/android/gallery3d/filtershow/pipeline/ProcessingTask.java b/src/com/android/gallery3d/filtershow/pipeline/ProcessingTask.java new file mode 100644 index 000000000..8d3e8110f --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/ProcessingTask.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2013 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 com.android.gallery3d.filtershow.pipeline; + +import android.content.Context; +import android.os.Handler; +import android.os.Message; + +public abstract class ProcessingTask { + private ProcessingTaskController mTaskController; + private Handler mProcessingHandler; + private Handler mResultHandler; + private int mType; + private static final int DELAY = 300; + + static interface Request {} + static interface Update {} + static interface Result {} + + public boolean postRequest(Request message) { + Message msg = mProcessingHandler.obtainMessage(mType); + msg.obj = message; + if (isPriorityTask()) { + if (mProcessingHandler.hasMessages(getType())) { + return false; + } + mProcessingHandler.sendMessageAtFrontOfQueue(msg); + } else if (isDelayedTask()) { + if (mProcessingHandler.hasMessages(getType())) { + mProcessingHandler.removeMessages(getType()); + } + mProcessingHandler.sendMessageDelayed(msg, DELAY); + } else { + mProcessingHandler.sendMessage(msg); + } + return true; + } + + public void postUpdate(Update message) { + Message msg = mResultHandler.obtainMessage(mType); + msg.obj = message; + msg.arg1 = ProcessingTaskController.UPDATE; + mResultHandler.sendMessage(msg); + } + + public void processRequest(Request message) { + Object result = doInBackground(message); + Message msg = mResultHandler.obtainMessage(mType); + msg.obj = result; + msg.arg1 = ProcessingTaskController.RESULT; + mResultHandler.sendMessage(msg); + } + + public void added(ProcessingTaskController taskController) { + mTaskController = taskController; + mResultHandler = taskController.getResultHandler(); + mProcessingHandler = taskController.getProcessingHandler(); + mType = taskController.getReservedType(); + } + + public int getType() { + return mType; + } + + public Context getContext() { + return mTaskController.getContext(); + } + + public abstract Result doInBackground(Request message); + public abstract void onResult(Result message); + public void onUpdate(Update message) {} + public boolean isPriorityTask() { return false; } + public boolean isDelayedTask() { return false; } +} diff --git a/src/com/android/gallery3d/filtershow/pipeline/ProcessingTaskController.java b/src/com/android/gallery3d/filtershow/pipeline/ProcessingTaskController.java new file mode 100644 index 000000000..b54bbb044 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/ProcessingTaskController.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2013 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 com.android.gallery3d.filtershow.pipeline; + +import android.content.Context; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.util.Log; + +import java.util.HashMap; + +public class ProcessingTaskController implements Handler.Callback { + private static final String LOGTAG = "ProcessingTaskController"; + + private Context mContext; + private HandlerThread mHandlerThread = null; + private Handler mProcessingHandler = null; + private int mCurrentType; + private HashMap<Integer, ProcessingTask> mTasks = new HashMap<Integer, ProcessingTask>(); + + public final static int RESULT = 1; + public final static int UPDATE = 2; + + private final Handler mResultHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + ProcessingTask task = mTasks.get(msg.what); + if (task != null) { + if (msg.arg1 == RESULT) { + task.onResult((ProcessingTask.Result) msg.obj); + } else if (msg.arg1 == UPDATE) { + task.onUpdate((ProcessingTask.Update) msg.obj); + } else { + Log.w(LOGTAG, "received unknown message! " + msg.arg1); + } + } + } + }; + + @Override + public boolean handleMessage(Message msg) { + ProcessingTask task = mTasks.get(msg.what); + if (task != null) { + task.processRequest((ProcessingTask.Request) msg.obj); + return true; + } + return false; + } + + public ProcessingTaskController(Context context) { + mContext = context; + mHandlerThread = new HandlerThread("ProcessingTaskController", + android.os.Process.THREAD_PRIORITY_FOREGROUND); + mHandlerThread.start(); + mProcessingHandler = new Handler(mHandlerThread.getLooper(), this); + } + + public Handler getProcessingHandler() { + return mProcessingHandler; + } + + public Handler getResultHandler() { + return mResultHandler; + } + + public int getReservedType() { + return mCurrentType++; + } + + public Context getContext() { + return mContext; + } + + public void add(ProcessingTask task) { + task.added(this); + mTasks.put(task.getType(), task); + } + + public void quit() { + mHandlerThread.quit(); + } +} diff --git a/src/com/android/gallery3d/filtershow/pipeline/RenderingRequest.java b/src/com/android/gallery3d/filtershow/pipeline/RenderingRequest.java new file mode 100644 index 000000000..ef4bb9bc0 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/RenderingRequest.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2013 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 com.android.gallery3d.filtershow.pipeline; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Rect; +import com.android.gallery3d.app.Log; +import com.android.gallery3d.filtershow.FilterShowActivity; +import com.android.gallery3d.filtershow.filters.FiltersManager; +import com.android.gallery3d.filtershow.imageshow.MasterImage; + +public class RenderingRequest { + private static final String LOGTAG = "RenderingRequest"; + private boolean mIsDirect = false; + private Bitmap mBitmap = null; + private ImagePreset mImagePreset = null; + private ImagePreset mOriginalImagePreset = null; + private RenderingRequestCaller mCaller = null; + private float mScaleFactor = 1.0f; + private Rect mBounds = null; + private Rect mDestination = null; + private int mType = FULL_RENDERING; + public static final int FULL_RENDERING = 0; + public static final int FILTERS_RENDERING = 1; + public static final int GEOMETRY_RENDERING = 2; + public static final int ICON_RENDERING = 3; + public static final int PARTIAL_RENDERING = 4; + public static final int HIGHRES_RENDERING = 5; + public static final int STYLE_ICON_RENDERING = 6; + + private static final Bitmap.Config mConfig = Bitmap.Config.ARGB_8888; + + public static void post(Context context, Bitmap source, ImagePreset preset, + int type, RenderingRequestCaller caller) { + RenderingRequest.post(context, source, preset, type, caller, null, null); + } + + public static void post(Context context, Bitmap source, ImagePreset preset, int type, + RenderingRequestCaller caller, Rect bounds, Rect destination) { + if (((type != PARTIAL_RENDERING && type != HIGHRES_RENDERING) && source == null) + || preset == null || caller == null) { + Log.v(LOGTAG, "something null: source: " + source + + " or preset: " + preset + " or caller: " + caller); + return; + } + RenderingRequest request = new RenderingRequest(); + Bitmap bitmap = null; + if (type == FULL_RENDERING + || type == GEOMETRY_RENDERING + || type == ICON_RENDERING + || type == STYLE_ICON_RENDERING) { + CachingPipeline pipeline = new CachingPipeline( + FiltersManager.getManager(), "Icon"); + bitmap = pipeline.renderGeometryIcon(source, preset); + } else if (type != PARTIAL_RENDERING && type != HIGHRES_RENDERING) { + bitmap = Bitmap.createBitmap(source.getWidth(), source.getHeight(), mConfig); + } + + request.setBitmap(bitmap); + ImagePreset passedPreset = new ImagePreset(preset); + request.setOriginalImagePreset(preset); + request.setScaleFactor(MasterImage.getImage().getScaleFactor()); + + if (type == PARTIAL_RENDERING) { + request.setBounds(bounds); + request.setDestination(destination); + passedPreset.setPartialRendering(true, bounds); + } + + request.setImagePreset(passedPreset); + request.setType(type); + request.setCaller(caller); + request.post(context); + } + + public void post(Context context) { + if (context instanceof FilterShowActivity) { + FilterShowActivity activity = (FilterShowActivity) context; + ProcessingService service = activity.getProcessingService(); + service.postRenderingRequest(this); + } + } + + public void markAvailable() { + if (mBitmap == null || mImagePreset == null + || mCaller == null) { + return; + } + mCaller.available(this); + } + + public boolean isDirect() { + return mIsDirect; + } + + public void setDirect(boolean isDirect) { + mIsDirect = isDirect; + } + + public Bitmap getBitmap() { + return mBitmap; + } + + public void setBitmap(Bitmap bitmap) { + mBitmap = bitmap; + } + + public ImagePreset getImagePreset() { + return mImagePreset; + } + + public void setImagePreset(ImagePreset imagePreset) { + mImagePreset = imagePreset; + } + + public int getType() { + return mType; + } + + public void setType(int type) { + mType = type; + } + + public void setCaller(RenderingRequestCaller caller) { + mCaller = caller; + } + + public Rect getBounds() { + return mBounds; + } + + public void setBounds(Rect bounds) { + mBounds = bounds; + } + + public void setScaleFactor(float scaleFactor) { + mScaleFactor = scaleFactor; + } + + public float getScaleFactor() { + return mScaleFactor; + } + + public Rect getDestination() { + return mDestination; + } + + public void setDestination(Rect destination) { + mDestination = destination; + } + + public ImagePreset getOriginalImagePreset() { + return mOriginalImagePreset; + } + + public void setOriginalImagePreset(ImagePreset originalImagePreset) { + mOriginalImagePreset = originalImagePreset; + } +} diff --git a/src/com/android/gallery3d/filtershow/pipeline/RenderingRequestCaller.java b/src/com/android/gallery3d/filtershow/pipeline/RenderingRequestCaller.java new file mode 100644 index 000000000..b978e7040 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/RenderingRequestCaller.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2013 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 com.android.gallery3d.filtershow.pipeline; + +public interface RenderingRequestCaller { + public void available(RenderingRequest request); +} diff --git a/src/com/android/gallery3d/filtershow/pipeline/RenderingRequestTask.java b/src/com/android/gallery3d/filtershow/pipeline/RenderingRequestTask.java new file mode 100644 index 000000000..7a83f7072 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/RenderingRequestTask.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2013 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 com.android.gallery3d.filtershow.pipeline; + +import android.graphics.Bitmap; +import com.android.gallery3d.filtershow.filters.FiltersManager; + +public class RenderingRequestTask extends ProcessingTask { + + private CachingPipeline mPreviewPipeline = null; + private boolean mPipelineIsOn = false; + + public void setPreviewScaleFactor(float previewScale) { + mPreviewPipeline.setPreviewScaleFactor(previewScale); + } + + static class Render implements Request { + RenderingRequest request; + } + + static class RenderResult implements Result { + RenderingRequest request; + } + + public RenderingRequestTask() { + mPreviewPipeline = new CachingPipeline( + FiltersManager.getManager(), "Normal"); + } + + public void setOriginal(Bitmap bitmap) { + mPreviewPipeline.setOriginal(bitmap); + mPipelineIsOn = true; + } + + public void stop() { + mPreviewPipeline.stop(); + } + + public void postRenderingRequest(RenderingRequest request) { + if (!mPipelineIsOn) { + return; + } + Render render = new Render(); + render.request = request; + postRequest(render); + } + + @Override + public Result doInBackground(Request message) { + RenderingRequest request = ((Render) message).request; + RenderResult result = null; + mPreviewPipeline.render(request); + result = new RenderResult(); + result.request = request; + return result; + } + + @Override + public void onResult(Result message) { + if (message == null) { + return; + } + RenderingRequest request = ((RenderResult) message).request; + request.markAvailable(); + } + +} diff --git a/src/com/android/gallery3d/filtershow/pipeline/SharedBuffer.java b/src/com/android/gallery3d/filtershow/pipeline/SharedBuffer.java new file mode 100644 index 000000000..98e69f60e --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/SharedBuffer.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2013 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 com.android.gallery3d.filtershow.pipeline; + +import android.graphics.Bitmap; + +public class SharedBuffer { + + private static final String LOGTAG = "SharedBuffer"; + + private volatile Buffer mProducer = null; + private volatile Buffer mConsumer = null; + private volatile Buffer mIntermediate = null; + + private volatile boolean mNeedsSwap = false; + private volatile boolean mNeedsRepaint = true; + + public void setProducer(Bitmap producer) { + Buffer buffer = new Buffer(producer); + synchronized (this) { + mProducer = buffer; + } + } + + public synchronized Buffer getProducer() { + return mProducer; + } + + public synchronized Buffer getConsumer() { + return mConsumer; + } + + public synchronized void swapProducer() { + Buffer intermediate = mIntermediate; + mIntermediate = mProducer; + mProducer = intermediate; + mNeedsSwap = true; + } + + public synchronized void swapConsumerIfNeeded() { + if (!mNeedsSwap) { + return; + } + Buffer intermediate = mIntermediate; + mIntermediate = mConsumer; + mConsumer = intermediate; + mNeedsSwap = false; + } + + public synchronized void invalidate() { + mNeedsRepaint = true; + } + + public synchronized boolean checkRepaintNeeded() { + if (mNeedsRepaint) { + mNeedsRepaint = false; + return true; + } + return false; + } + +} + diff --git a/src/com/android/gallery3d/filtershow/pipeline/SharedPreset.java b/src/com/android/gallery3d/filtershow/pipeline/SharedPreset.java new file mode 100644 index 000000000..3f850fed2 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/SharedPreset.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2013 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 com.android.gallery3d.filtershow.pipeline; + +public class SharedPreset { + + private volatile ImagePreset mProducerPreset = null; + private volatile ImagePreset mConsumerPreset = null; + private volatile ImagePreset mIntermediatePreset = null; + + public synchronized void enqueuePreset(ImagePreset preset) { + if (mProducerPreset == null || (!mProducerPreset.same(preset))) { + mProducerPreset = new ImagePreset(preset); + } else { + mProducerPreset.updateWith(preset); + } + ImagePreset temp = mIntermediatePreset; + mIntermediatePreset = mProducerPreset; + mProducerPreset = temp; + } + + public synchronized ImagePreset dequeuePreset() { + ImagePreset temp = mConsumerPreset; + mConsumerPreset = mIntermediatePreset; + mIntermediatePreset = temp; + return mConsumerPreset; + } +} diff --git a/src/com/android/gallery3d/filtershow/pipeline/UpdatePreviewTask.java b/src/com/android/gallery3d/filtershow/pipeline/UpdatePreviewTask.java new file mode 100644 index 000000000..406cc9bf5 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/UpdatePreviewTask.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2013 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 com.android.gallery3d.filtershow.pipeline; + +import android.graphics.Bitmap; +import com.android.gallery3d.filtershow.filters.FiltersManager; +import com.android.gallery3d.filtershow.imageshow.MasterImage; + +public class UpdatePreviewTask extends ProcessingTask { + private CachingPipeline mPreviewPipeline = null; + private boolean mHasUnhandledPreviewRequest = false; + private boolean mPipelineIsOn = false; + + public UpdatePreviewTask() { + mPreviewPipeline = new CachingPipeline( + FiltersManager.getPreviewManager(), "Preview"); + } + + public void setOriginal(Bitmap bitmap) { + mPreviewPipeline.setOriginal(bitmap); + mPipelineIsOn = true; + } + + public void updatePreview() { + if (!mPipelineIsOn) { + return; + } + mHasUnhandledPreviewRequest = true; + if (postRequest(null)) { + mHasUnhandledPreviewRequest = false; + } + } + + @Override + public boolean isPriorityTask() { + return true; + } + + @Override + public Result doInBackground(Request message) { + SharedBuffer buffer = MasterImage.getImage().getPreviewBuffer(); + SharedPreset preset = MasterImage.getImage().getPreviewPreset(); + ImagePreset renderingPreset = preset.dequeuePreset(); + if (renderingPreset != null) { + mPreviewPipeline.compute(buffer, renderingPreset, 0); + // set the preset we used in the buffer for later inspection UI-side + buffer.getProducer().setPreset(renderingPreset); + buffer.getProducer().sync(); + buffer.swapProducer(); // push back the result + } + return null; + } + + @Override + public void onResult(Result message) { + MasterImage.getImage().notifyObservers(); + if (mHasUnhandledPreviewRequest) { + updatePreview(); + } + } + + public void setPipelineIsOn(boolean pipelineIsOn) { + mPipelineIsOn = pipelineIsOn; + } +} |