blob: 273c29532d28035ccce7081296282f8aaf27aec2 [file] [log] [blame]
Yuli Huang6a12ad72011-09-12 22:25:30 +08001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.gallery3d.photoeditor;
18
19import android.graphics.Bitmap;
Yuli Huang738e82e2011-11-04 16:39:50 +080020import android.os.Bundle;
Yuli Huang6a12ad72011-09-12 22:25:30 +080021
22import com.android.gallery3d.photoeditor.filters.Filter;
23
Yuli Huang738e82e2011-11-04 16:39:50 +080024import java.util.ArrayList;
Yuli Huang6a12ad72011-09-12 22:25:30 +080025import java.util.Stack;
26
27/**
28 * A stack of filters to be applied onto a photo.
29 */
30public class FilterStack {
31
32 /**
33 * Listener of stack changes.
34 */
35 public interface StackListener {
36
37 void onStackChanged(boolean canUndo, boolean canRedo);
38 }
39
Yuli Huang738e82e2011-11-04 16:39:50 +080040 private static final String APPLIED_STACK_KEY = "applied_stack";
41 private static final String REDO_STACK_KEY = "redo_stack";
42
Yuli Huang6a12ad72011-09-12 22:25:30 +080043 private final Stack<Filter> appliedStack = new Stack<Filter>();
44 private final Stack<Filter> redoStack = new Stack<Filter>();
45
46 // Use two photo buffers as in and out in turns to apply filters in the stack.
47 private final Photo[] buffers = new Photo[2];
48 private final PhotoView photoView;
49 private final StackListener stackListener;
50
Yuli Huang6a12ad72011-09-12 22:25:30 +080051 private Photo source;
52 private Runnable queuedTopFilterChange;
Yuli Huang738e82e2011-11-04 16:39:50 +080053 private boolean outputTopFilter;
Yuli Huang6a12ad72011-09-12 22:25:30 +080054 private volatile boolean paused;
55
Yuli Huang738e82e2011-11-04 16:39:50 +080056 public FilterStack(PhotoView photoView, StackListener stackListener, Bundle savedState) {
Yuli Huang6a12ad72011-09-12 22:25:30 +080057 this.photoView = photoView;
58 this.stackListener = stackListener;
Yuli Huang738e82e2011-11-04 16:39:50 +080059 if (savedState != null) {
60 appliedStack.addAll(getFilters(savedState, APPLIED_STACK_KEY));
61 redoStack.addAll(getFilters(savedState, REDO_STACK_KEY));
62 outputTopFilter = true;
Yuli Huang90b7a7a2011-11-09 15:10:45 +080063 stackListener.onStackChanged(!appliedStack.empty(), !redoStack.empty());
Yuli Huang738e82e2011-11-04 16:39:50 +080064 }
65 }
66
67 private ArrayList<Filter> getFilters(Bundle savedState, String key) {
68 // Infer Filter array-list from the Parcelable array-list by the specified returned type.
69 return savedState.getParcelableArrayList(key);
70 }
71
72 public void saveStacks(Bundle outState) {
73 outState.putParcelableArrayList(APPLIED_STACK_KEY, new ArrayList<Filter>(appliedStack));
74 outState.putParcelableArrayList(REDO_STACK_KEY, new ArrayList<Filter>(redoStack));
Yuli Huang6a12ad72011-09-12 22:25:30 +080075 }
76
Yuli Huang6a12ad72011-09-12 22:25:30 +080077 private void reallocateBuffer(int target) {
78 int other = target ^ 1;
79 buffers[target] = Photo.create(buffers[other].width(), buffers[other].height());
80 }
81
82 private void invalidate() {
83 // In/out buffers need redrawn by re-applying filters on source photo.
Yuli Huangc7e39d12011-10-05 16:22:10 +080084 for (int i = 0; i < buffers.length; i++) {
85 if (buffers[i] != null) {
86 buffers[i].clear();
87 buffers[i] = null;
88 }
89 }
Yuli Huang6a12ad72011-09-12 22:25:30 +080090 if (source != null) {
91 buffers[0] = Photo.create(source.width(), source.height());
92 reallocateBuffer(1);
93
Yuli Huange0e82f12011-10-10 00:55:26 +080094 // Source photo will be displayed if there is no filter stacked.
Yuli Huang6a12ad72011-09-12 22:25:30 +080095 Photo photo = source;
Yuli Huang738e82e2011-11-04 16:39:50 +080096 int size = outputTopFilter ? appliedStack.size() : appliedStack.size() - 1;
Yuli Huange0e82f12011-10-10 00:55:26 +080097 for (int i = 0; i < size && !paused; i++) {
Yuli Huang6a12ad72011-09-12 22:25:30 +080098 photo = runFilter(i);
99 }
Yuli Huang738e82e2011-11-04 16:39:50 +0800100 // Clear photo-view transformation when the top filter will be outputted.
101 photoView.setPhoto(photo, outputTopFilter);
Yuli Huang6a12ad72011-09-12 22:25:30 +0800102 }
103 }
104
105 private void invalidateTopFilter() {
106 if (!appliedStack.empty()) {
Yuli Huang738e82e2011-11-04 16:39:50 +0800107 outputTopFilter = true;
Yuli Huange0e82f12011-10-10 00:55:26 +0800108 photoView.setPhoto(runFilter(appliedStack.size() - 1), true);
Yuli Huang6a12ad72011-09-12 22:25:30 +0800109 }
110 }
111
112 private Photo runFilter(int filterIndex) {
113 int out = getOutBufferIndex(filterIndex);
114 Photo input = (filterIndex > 0) ? buffers[out ^ 1] : source;
115 if ((input != null) && (buffers[out] != null)) {
116 if (!buffers[out].matchDimension(input)) {
117 buffers[out].clear();
118 reallocateBuffer(out);
119 }
Yuli Huangc7e39d12011-10-05 16:22:10 +0800120 appliedStack.get(filterIndex).process(input, buffers[out]);
Ruei-sung Line5c8ed72012-05-31 11:59:31 -0700121 nativeEglSetFenceAndWait();
Yuli Huang6a12ad72011-09-12 22:25:30 +0800122 return buffers[out];
123 }
124 return null;
125 }
126
127 private int getOutBufferIndex(int filterIndex) {
128 // buffers[0] and buffers[1] are swapped in turns as the in/out buffers for
129 // processing stacked filters. For example, the first filter reads buffer[0] and
130 // writes buffer[1]; the second filter then reads buffer[1] and writes buffer[0].
131 // The returned index should only be used when the applied filter stack isn't empty.
132 return (filterIndex + 1) % 2;
133 }
134
135 private void callbackDone(final OnDoneCallback callback) {
Yuli Huangae70ae42011-10-01 02:05:06 +0800136 // GL thread calls back to report UI thread the task is done.
Yuli Huang6a12ad72011-09-12 22:25:30 +0800137 photoView.post(new Runnable() {
138
139 @Override
140 public void run() {
141 callback.onDone();
142 }
143 });
144 }
145
146 private void stackChanged() {
Yuli Huangae70ae42011-10-01 02:05:06 +0800147 // GL thread calls back to report UI thread the stack is changed.
148 final boolean canUndo = !appliedStack.empty();
149 final boolean canRedo = !redoStack.empty();
150 photoView.post(new Runnable() {
151
152 @Override
153 public void run() {
154 stackListener.onStackChanged(canUndo, canRedo);
155 }
156 });
Yuli Huang6a12ad72011-09-12 22:25:30 +0800157 }
158
Yuli Huang738e82e2011-11-04 16:39:50 +0800159 public void getOutputBitmap(final OnDoneBitmapCallback callback) {
Yuli Huang6a12ad72011-09-12 22:25:30 +0800160 photoView.queue(new Runnable() {
161
162 @Override
163 public void run() {
Yuli Huang738e82e2011-11-04 16:39:50 +0800164 int filterIndex = appliedStack.size() - (outputTopFilter ? 1 : 2);
Yuli Huange0e82f12011-10-10 00:55:26 +0800165 Photo photo = (filterIndex < 0) ? source : buffers[getOutBufferIndex(filterIndex)];
Yuli Huang6a12ad72011-09-12 22:25:30 +0800166 final Bitmap bitmap = (photo != null) ? photo.save() : null;
167 photoView.post(new Runnable() {
168
169 @Override
170 public void run() {
171 callback.onDone(bitmap);
172 }
173 });
174 }
175 });
176 }
177
178 public void setPhotoSource(final Bitmap bitmap, final OnDoneCallback callback) {
179 photoView.queue(new Runnable() {
180
181 @Override
182 public void run() {
183 source = Photo.create(bitmap);
184 invalidate();
185 callbackDone(callback);
186 }
187 });
188 }
189
Yuli Huange0e82f12011-10-10 00:55:26 +0800190 private void pushFilterInternal(Filter filter) {
191 appliedStack.push(filter);
Yuli Huang738e82e2011-11-04 16:39:50 +0800192 outputTopFilter = false;
Yuli Huange0e82f12011-10-10 00:55:26 +0800193 stackChanged();
194 }
195
Yuli Huang6a12ad72011-09-12 22:25:30 +0800196 public void pushFilter(final Filter filter) {
197 photoView.queue(new Runnable() {
198
199 @Override
200 public void run() {
201 while (!redoStack.empty()) {
202 redoStack.pop().release();
203 }
Yuli Huange0e82f12011-10-10 00:55:26 +0800204 pushFilterInternal(filter);
Yuli Huang6a12ad72011-09-12 22:25:30 +0800205 }
206 });
207 }
208
209 public void undo(final OnDoneCallback callback) {
210 photoView.queue(new Runnable() {
211
212 @Override
213 public void run() {
214 if (!appliedStack.empty()) {
215 redoStack.push(appliedStack.pop());
216 stackChanged();
217 invalidate();
218 }
219 callbackDone(callback);
220 }
221 });
222 }
223
224 public void redo(final OnDoneCallback callback) {
225 photoView.queue(new Runnable() {
226
227 @Override
228 public void run() {
229 if (!redoStack.empty()) {
Yuli Huange0e82f12011-10-10 00:55:26 +0800230 pushFilterInternal(redoStack.pop());
Yuli Huang6a12ad72011-09-12 22:25:30 +0800231 invalidateTopFilter();
232 }
233 callbackDone(callback);
234 }
235 });
236 }
237
238 public void topFilterChanged(final OnDoneCallback callback) {
239 // Remove the outdated top-filter change before queuing a new one.
240 if (queuedTopFilterChange != null) {
241 photoView.remove(queuedTopFilterChange);
242 }
243 queuedTopFilterChange = new Runnable() {
244
245 @Override
246 public void run() {
247 invalidateTopFilter();
248 callbackDone(callback);
249 }
250 };
251 photoView.queue(queuedTopFilterChange);
252 }
253
254 public void onPause() {
255 // Flush pending queued operations and release effect-context before GL context is lost.
Yuli Huangc7e39d12011-10-05 16:22:10 +0800256 // Use the flag to break from lengthy invalidate() in GL thread for not blocking onPause().
Yuli Huang6a12ad72011-09-12 22:25:30 +0800257 paused = true;
258 photoView.flush();
259 photoView.queueEvent(new Runnable() {
260
261 @Override
262 public void run() {
Yuli Huangc7e39d12011-10-05 16:22:10 +0800263 Filter.releaseContext();
Yuli Huangeb83d832011-10-11 21:23:22 +0800264 // Textures will be automatically deleted when GL context is lost.
265 photoView.setPhoto(null, false);
266 source = null;
Yuli Huangc7e39d12011-10-05 16:22:10 +0800267 for (int i = 0; i < buffers.length; i++) {
Yuli Huangc7e39d12011-10-05 16:22:10 +0800268 buffers[i] = null;
Yuli Huang6a12ad72011-09-12 22:25:30 +0800269 }
Yuli Huang6a12ad72011-09-12 22:25:30 +0800270 }
271 });
272 photoView.onPause();
273 }
274
275 public void onResume() {
276 photoView.onResume();
Yuli Huang6a12ad72011-09-12 22:25:30 +0800277 paused = false;
278 }
Ruei-sung Line5c8ed72012-05-31 11:59:31 -0700279
280 static {
281 System.loadLibrary("jni_eglfence");
282 }
283
284 private native void nativeEglSetFenceAndWait();
Yuli Huang6a12ad72011-09-12 22:25:30 +0800285}