diff options
Diffstat (limited to 'src/com/android/gallery3d/photoeditor/FilterStack.java')
-rw-r--r-- | src/com/android/gallery3d/photoeditor/FilterStack.java | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/photoeditor/FilterStack.java b/src/com/android/gallery3d/photoeditor/FilterStack.java new file mode 100644 index 000000000..3b35aa3e1 --- /dev/null +++ b/src/com/android/gallery3d/photoeditor/FilterStack.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2010 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.photoeditor; + +import android.graphics.Bitmap; +import android.media.effect.EffectContext; + +import com.android.gallery3d.photoeditor.filters.Filter; + +import java.util.Stack; + +/** + * A stack of filters to be applied onto a photo. + */ +public class FilterStack { + + /** + * Listener of stack changes. + */ + public interface StackListener { + + void onStackChanged(boolean canUndo, boolean canRedo); + } + + private final Stack<Filter> appliedStack = new Stack<Filter>(); + private final Stack<Filter> redoStack = new Stack<Filter>(); + + // Use two photo buffers as in and out in turns to apply filters in the stack. + private final Photo[] buffers = new Photo[2]; + private final PhotoView photoView; + private final StackListener stackListener; + + private EffectContext effectContext; + private Photo source; + private Runnable queuedTopFilterChange; + private volatile boolean paused; + + public FilterStack(PhotoView photoView, StackListener stackListener) { + this.photoView = photoView; + this.stackListener = stackListener; + } + + private void clearBuffers() { + for (int i = 0; i < buffers.length; i++) { + if (buffers[i] != null) { + buffers[i].clear(); + buffers[i] = null; + } + } + } + + private void reallocateBuffer(int target) { + int other = target ^ 1; + buffers[target] = Photo.create(buffers[other].width(), buffers[other].height()); + } + + private void invalidate() { + // In/out buffers need redrawn by re-applying filters on source photo. + clearBuffers(); + if (source != null) { + buffers[0] = Photo.create(source.width(), source.height()); + reallocateBuffer(1); + + Photo photo = source; + for (int i = 0; i < appliedStack.size() && !paused; i++) { + photo = runFilter(i); + } + // Source photo will be displayed if there is no filter stacked. + photoView.setPhoto(photo); + } + } + + private void invalidateTopFilter() { + if (!appliedStack.empty()) { + photoView.setPhoto(runFilter(appliedStack.size() - 1)); + } + } + + private Photo runFilter(int filterIndex) { + int out = getOutBufferIndex(filterIndex); + Photo input = (filterIndex > 0) ? buffers[out ^ 1] : source; + if ((input != null) && (buffers[out] != null)) { + if (!buffers[out].matchDimension(input)) { + buffers[out].clear(); + reallocateBuffer(out); + } + appliedStack.get(filterIndex).process(effectContext, input, buffers[out]); + return buffers[out]; + } + return null; + } + + private int getOutBufferIndex(int filterIndex) { + // buffers[0] and buffers[1] are swapped in turns as the in/out buffers for + // processing stacked filters. For example, the first filter reads buffer[0] and + // writes buffer[1]; the second filter then reads buffer[1] and writes buffer[0]. + // The returned index should only be used when the applied filter stack isn't empty. + return (filterIndex + 1) % 2; + } + + private void callbackDone(final OnDoneCallback callback) { + // Call back in UI thread to report done. + photoView.post(new Runnable() { + + @Override + public void run() { + callback.onDone(); + } + }); + } + + private void stackChanged() { + stackListener.onStackChanged(!appliedStack.empty(), !redoStack.empty()); + } + + public void saveBitmap(final OnDoneBitmapCallback callback) { + photoView.queue(new Runnable() { + + @Override + public void run() { + Photo photo = appliedStack.empty() ? + null : buffers[getOutBufferIndex(appliedStack.size() - 1)]; + final Bitmap bitmap = (photo != null) ? photo.save() : null; + photoView.post(new Runnable() { + + @Override + public void run() { + callback.onDone(bitmap); + } + }); + } + }); + } + + public void setPhotoSource(final Bitmap bitmap, final OnDoneCallback callback) { + photoView.queue(new Runnable() { + + @Override + public void run() { + source = Photo.create(bitmap); + invalidate(); + callbackDone(callback); + } + }); + } + + public void pushFilter(final Filter filter) { + photoView.queue(new Runnable() { + + @Override + public void run() { + while (!redoStack.empty()) { + redoStack.pop().release(); + } + appliedStack.push(filter); + stackChanged(); + } + }); + } + + public void undo(final OnDoneCallback callback) { + photoView.queue(new Runnable() { + + @Override + public void run() { + if (!appliedStack.empty()) { + redoStack.push(appliedStack.pop()); + stackChanged(); + invalidate(); + } + callbackDone(callback); + } + }); + } + + public void redo(final OnDoneCallback callback) { + photoView.queue(new Runnable() { + + @Override + public void run() { + if (!redoStack.empty()) { + appliedStack.push(redoStack.pop()); + stackChanged(); + invalidateTopFilter(); + } + callbackDone(callback); + } + }); + } + + public void topFilterChanged(final OnDoneCallback callback) { + // Remove the outdated top-filter change before queuing a new one. + if (queuedTopFilterChange != null) { + photoView.remove(queuedTopFilterChange); + } + queuedTopFilterChange = new Runnable() { + + @Override + public void run() { + invalidateTopFilter(); + callbackDone(callback); + } + }; + photoView.queue(queuedTopFilterChange); + } + + public void onPause() { + // Flush pending queued operations and release effect-context before GL context is lost. + // Use pause-flag to avoid lengthy runnable in GL thread blocking onPause(). + paused = true; + photoView.flush(); + photoView.queueEvent(new Runnable() { + + @Override + public void run() { + if (effectContext != null) { + effectContext.release(); + effectContext = null; + } + photoView.setPhoto(null); + clearBuffers(); + } + }); + photoView.onPause(); + } + + public void onResume() { + photoView.onResume(); + photoView.queue(new Runnable() { + + @Override + public void run() { + // Create effect context after GL context is created or recreated. + effectContext = EffectContext.createWithCurrentGlContext(); + } + }); + paused = false; + } +} |