Home | History | Annotate | Download | only in photoeditor
      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