/* * 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.wistron.WiEditor; import android.graphics.Bitmap; import android.util.Log; 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 String TAG="FilterStack"; 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 Photo source; private Runnable queuedTopFilterChange; private boolean topFilterOutputted; private volatile boolean paused; public FilterStack(PhotoView photoView, StackListener stackListener) { this.photoView = photoView; this.stackListener = stackListener; } 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. for (int i = 0; i < buffers.length; i++) { if (buffers[i] != null) { buffers[i].clear(); buffers[i] = null; } } if (source != null) { buffers[0] = Photo.create(source.width(), source.height()); photoView.setPhoto(source, topFilterOutputted); } } private void invalidateTopFilter() { if (!appliedStack.empty()) { photoView.setPhoto(runFilter(appliedStack.size() - 1), true); topFilterOutputted = true; } } void pushFilterRedeye(Filter filter) { if(appliedStack.size()==0) { appliedStack.push(filter); } topFilterOutputted = false; } public void updateUI() { photoView.queue(new Runnable() { @Override public void run() { for (int i = 0; i < buffers.length; i++) { if (buffers[i] != null) { buffers[i].clear(); buffers[i] = null; } } if (source != null) { buffers[0] = Photo.create(source.width(), source.height()); Photo input = source; if ((input != null) && (buffers[0] != null)) { appliedStack.get(0).process(input, buffers[0]); photoView.setPhoto( buffers[0], topFilterOutputted); } } } }); } 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(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) { // GL thread calls back to report UI thread the task is done. photoView.post(new Runnable() { @Override public void run() { callback.onDone(); } }); } private void stackChanged() { // GL thread calls back to report UI thread the stack is changed. final boolean canUndo = !appliedStack.empty(); final boolean canRedo = !redoStack.empty(); photoView.post(new Runnable() { @Override public void run() { stackListener.onStackChanged(canUndo, canRedo); } }); } public void saveRedEyeBitmap(final OnDoneBitmapCallback callback) { photoView.queue(new Runnable() { @Override public void run() { if(appliedStack.size()==0) { photoView.post(new Runnable() { @Override public void run() { callback.onDone(null); } }); return; } Photo photo = buffers[0]; final Bitmap bitmap = (photo != null) ? photo.save() : null; photoView.post(new Runnable() { @Override public void run() { callback.onDone(bitmap); } }); } }); } public void saveBitmap(final OnDoneBitmapCallback callback) { photoView.queue(new Runnable() { @Override public void run() { int filterIndex = appliedStack.size() - (topFilterOutputted ? 1 : 2); Photo photo = (filterIndex < 0) ? source : buffers[getOutBufferIndex(filterIndex)]; 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); } }); } private void pushFilterInternal(Filter filter) { appliedStack.push(filter); topFilterOutputted = false; stackChanged(); } public void pushFilter(final Filter filter) { photoView.queue(new Runnable() { @Override public void run() { while (!redoStack.empty()) { redoStack.pop().release(); } pushFilterInternal(filter); } }); } 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()) { pushFilterInternal(redoStack.pop()); 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 the flag to break from lengthy invalidate() in GL thread for not blocking onPause(). paused = true; photoView.flush(); photoView.queueEvent(new Runnable() { @Override public void run() { Filter.releaseContext(); // Textures will be automatically deleted when GL context is lost. photoView.setPhoto(null, false); source = null; for (int i = 0; i < buffers.length; i++) { buffers[i] = null; } } }); photoView.onPause(); } public void onResume() { photoView.onResume(); paused = false; } }