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 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 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 213 public void getSourceCopy(PhotoOutputCallback callback) { 214 workerHandler.sendMessage(workerHandler.obtainMessage(COPY_SOURCE, callback)); 215 } 216 217 public void getResultCopy(PhotoOutputCallback callback) { 218 workerHandler.sendMessage(workerHandler.obtainMessage(COPY_RESULT, callback)); 219 } 220 221 public void setPhotoSource(Photo source) { 222 workerHandler.sendMessage(workerHandler.obtainMessage(SET_SOURCE, source)); 223 } 224 225 public void clearPhotoSource() { 226 workerHandler.sendMessage(workerHandler.obtainMessage(CLEAR_SOURCE)); 227 } 228 229 public void clearStacks() { 230 workerHandler.sendMessage(workerHandler.obtainMessage(CLEAR_STACKS)); 231 } 232 233 public void pushFilter(Filter filter) { 234 workerHandler.sendMessage(workerHandler.obtainMessage(PUSH_FILTER, filter)); 235 } 236 237 public void undo(PhotoOutputCallback callback) { 238 workerHandler.sendMessage(workerHandler.obtainMessage(UNDO, callback)); 239 } 240 241 public void redo(PhotoOutputCallback callback) { 242 workerHandler.sendMessage(workerHandler.obtainMessage(REDO, callback)); 243 } 244 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 254 public synchronized void setStackListener(StackListener listener) { 255 stackListener = listener; 256 } 257 258 private synchronized void stackChanged() { 259 if (stackListener != null) { 260 stackListener.onStackChanged(!appliedStack.empty(), !redoStack.empty()); 261 } 262 } 263 } 264