• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.photoeditor;
18 
19 import android.graphics.Bitmap;
20 import android.os.Handler;
21 import android.os.HandlerThread;
22 import android.os.Message;
23 
24 import com.android.photoeditor.filters.Filter;
25 
26 import java.util.Stack;
27 import java.util.Vector;
28 
29 /**
30  * A stack of filters to be applied onto a photo.
31  */
32 public class FilterStack {
33 
34     /**
35      * Listener of stack changes.
36      */
37     public interface StackListener {
38 
onStackChanged(boolean canUndo, boolean canRedo)39         void onStackChanged(boolean canUndo, boolean canRedo);
40     }
41 
42     private static class OutputMessageObj {
43         PhotoOutputCallback callback;
44         Photo result;
45     }
46 
47     private static final int COPY_SOURCE = 1;
48     private static final int COPY_RESULT = 2;
49     private static final int SET_SOURCE = 3;
50     private static final int CLEAR_SOURCE = 4;
51     private static final int CLEAR_STACKS = 5;
52     private static final int PUSH_FILTER = 6;
53     private static final int UNDO = 7;
54     private static final int REDO = 8;
55     private static final int TOP_FILTER_CHANGE = 9;
56     private static final int OUTPUT = 10;
57 
58     private final Stack<Filter> appliedStack = new Stack<Filter>();
59     private final Stack<Filter> redoStack = new Stack<Filter>();
60     private final Vector<Message> pendingMessages = new Vector<Message>();
61     private final Handler mainHandler;
62     private final Handler workerHandler;
63 
64     // Use two photo buffers as in and out in turns to apply filters in the stack.
65     private final Photo[] buffers = new Photo[2];
66 
67     private Photo source;
68     private StackListener stackListener;
69 
FilterStack()70     public FilterStack() {
71         HandlerThread workerThread = new HandlerThread("FilterStackWorker");
72         workerThread.start();
73 
74         mainHandler = new Handler() {
75 
76             @Override
77             public void handleMessage(Message msg) {
78                 switch (msg.what) {
79                     case OUTPUT:
80                         OutputMessageObj obj = (OutputMessageObj) msg.obj;
81                         obj.callback.onReady(obj.result);
82                         break;
83                 }
84             }
85         };
86         workerHandler = new Handler(workerThread.getLooper()) {
87 
88             private void output(PhotoOutputCallback callback, Photo target) {
89                 // Copy target photo in rgb-565 format to update photo-view or save.
90                 OutputMessageObj obj = new OutputMessageObj();
91                 obj.callback = callback;
92                 obj.result = (target != null) ? target.copy(Bitmap.Config.RGB_565) : null;
93                 mainHandler.sendMessage(mainHandler.obtainMessage(OUTPUT, obj));
94             }
95 
96             private void clearBuffers() {
97                 pendingMessages.clear();
98                 workerHandler.removeMessages(TOP_FILTER_CHANGE);
99                 mainHandler.removeMessages(OUTPUT);
100                 for (int i = 0; i < buffers.length; i++) {
101                     if (buffers[i] != null) {
102                         buffers[i].clear();
103                         buffers[i] = null;
104                     }
105                 }
106             }
107 
108             private void reallocateBuffer(int target) {
109                 int other = target ^ 1;
110                 buffers[target] = Photo.create(Bitmap.createBitmap(
111                         buffers[other].width(), buffers[other].height(), Bitmap.Config.ARGB_8888));
112             }
113 
114             private void invalidate() {
115                 // In/out buffers need redrawn by reloading source photo and re-applying filters.
116                 clearBuffers();
117                 buffers[0] = (source != null) ? source.copy(Bitmap.Config.ARGB_8888) : null;
118                 if (buffers[0] != null) {
119                     reallocateBuffer(1);
120 
121                     int out = 1;
122                     for (Filter filter : appliedStack) {
123                         runFilter(filter, out);
124                         out = out ^ 1;
125                     }
126                 }
127             }
128 
129             private void runFilter(Filter filter, int out) {
130                 if ((buffers[0] != null) && (buffers[1] != null)) {
131                     int in = out ^ 1;
132                     filter.process(buffers[in], buffers[out]);
133                     if (!buffers[in].matchDimension(buffers[out])) {
134                         buffers[in].clear();
135                         reallocateBuffer(in);
136                     }
137                 }
138             }
139 
140             private int getOutBufferIndex() {
141                 // buffers[0] and buffers[1] are swapped in turns as the in/out buffers for
142                 // processing stacked filters. For example, the first filter reads buffer[0] and
143                 // writes buffer[1]; the second filter then reads buffer[1] and writes buffer[0].
144                 return appliedStack.size() % 2;
145             }
146 
147             @Override
148             public void handleMessage(Message msg) {
149                 switch (msg.what) {
150                     case COPY_SOURCE:
151                         output((PhotoOutputCallback) msg.obj, source);
152                         break;
153 
154                     case COPY_RESULT:
155                         output((PhotoOutputCallback) msg.obj, buffers[getOutBufferIndex()]);
156                         break;
157 
158                     case SET_SOURCE:
159                         source = (Photo) msg.obj;
160                         invalidate();
161                         break;
162 
163                     case CLEAR_SOURCE:
164                         if (source != null) {
165                             source.clear();
166                             source = null;
167                         }
168                         clearBuffers();
169                         break;
170 
171                     case CLEAR_STACKS:
172                         redoStack.clear();
173                         appliedStack.clear();
174                         break;
175 
176                     case PUSH_FILTER:
177                         redoStack.clear();
178                         appliedStack.push((Filter) msg.obj);
179                         stackChanged();
180                         break;
181 
182                     case UNDO:
183                         if (!appliedStack.empty()) {
184                             redoStack.push(appliedStack.pop());
185                             stackChanged();
186                             invalidate();
187                         }
188                         output((PhotoOutputCallback) msg.obj, buffers[getOutBufferIndex()]);
189                         break;
190 
191                     case REDO:
192                         if (!redoStack.empty()) {
193                             Filter filter = redoStack.pop();
194                             appliedStack.push(filter);
195                             stackChanged();
196                             runFilter(filter, getOutBufferIndex());
197                         }
198                         output((PhotoOutputCallback) msg.obj, buffers[getOutBufferIndex()]);
199                         break;
200 
201                     case TOP_FILTER_CHANGE:
202                         if (pendingMessages.remove(msg) && !appliedStack.empty()) {
203                             int out = getOutBufferIndex();
204                             runFilter(appliedStack.peek(), out);
205                             output((PhotoOutputCallback) msg.obj, buffers[out]);
206                         }
207                         break;
208                 }
209             }
210         };
211     }
212 
getSourceCopy(PhotoOutputCallback callback)213     public void getSourceCopy(PhotoOutputCallback callback) {
214         workerHandler.sendMessage(workerHandler.obtainMessage(COPY_SOURCE, callback));
215     }
216 
getResultCopy(PhotoOutputCallback callback)217     public void getResultCopy(PhotoOutputCallback callback) {
218         workerHandler.sendMessage(workerHandler.obtainMessage(COPY_RESULT, callback));
219     }
220 
setPhotoSource(Photo source)221     public void setPhotoSource(Photo source) {
222         workerHandler.sendMessage(workerHandler.obtainMessage(SET_SOURCE, source));
223     }
224 
clearPhotoSource()225     public void clearPhotoSource() {
226         workerHandler.sendMessage(workerHandler.obtainMessage(CLEAR_SOURCE));
227     }
228 
clearStacks()229     public void clearStacks() {
230         workerHandler.sendMessage(workerHandler.obtainMessage(CLEAR_STACKS));
231     }
232 
pushFilter(Filter filter)233     public void pushFilter(Filter filter) {
234         workerHandler.sendMessage(workerHandler.obtainMessage(PUSH_FILTER, filter));
235     }
236 
undo(PhotoOutputCallback callback)237     public void undo(PhotoOutputCallback callback) {
238         workerHandler.sendMessage(workerHandler.obtainMessage(UNDO, callback));
239     }
240 
redo(PhotoOutputCallback callback)241     public void redo(PhotoOutputCallback callback) {
242         workerHandler.sendMessage(workerHandler.obtainMessage(REDO, callback));
243     }
244 
topFilterChanged(PhotoOutputCallback callback)245     public void topFilterChanged(PhotoOutputCallback callback) {
246         // Flush outdated top-filter messages before sending new ones.
247         Message msg = workerHandler.obtainMessage(TOP_FILTER_CHANGE, callback);
248         pendingMessages.clear();
249         pendingMessages.add(msg);
250         workerHandler.removeMessages(TOP_FILTER_CHANGE);
251         workerHandler.sendMessage(msg);
252     }
253 
setStackListener(StackListener listener)254     public synchronized void setStackListener(StackListener listener) {
255         stackListener = listener;
256     }
257 
stackChanged()258     private synchronized void stackChanged() {
259         if (stackListener != null) {
260             stackListener.onStackChanged(!appliedStack.empty(), !redoStack.empty());
261         }
262     }
263 }
264