1/*
2 * Copyright (C) 2013 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.filtershow.pipeline;
18
19import android.graphics.Bitmap;
20import android.util.Log;
21
22import com.android.gallery3d.filtershow.cache.BitmapCache;
23import com.android.gallery3d.filtershow.filters.FilterRepresentation;
24import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils;
25
26import java.util.ArrayList;
27import java.util.Vector;
28
29public class CacheProcessing {
30    private static final String LOGTAG = "CacheProcessing";
31    private static final boolean DEBUG = false;
32    private static final boolean NO_CACHING = false;
33    private Vector<CacheStep> mSteps = new Vector<CacheStep>();
34
35    static class CacheStep {
36        ArrayList<FilterRepresentation> representations;
37        Bitmap cache;
38
39        public CacheStep() {
40            representations = new ArrayList<FilterRepresentation>();
41        }
42
43        public void add(FilterRepresentation representation) {
44            representations.add(representation);
45        }
46
47        public boolean canMergeWith(FilterRepresentation representation) {
48            for (FilterRepresentation rep : representations) {
49                if (!rep.canMergeWith(representation)) {
50                    return false;
51                }
52            }
53            return true;
54        }
55
56        public boolean equals(CacheStep step) {
57            if (representations.size() != step.representations.size()) {
58                return false;
59            }
60            for (int i = 0; i < representations.size(); i++) {
61                FilterRepresentation r1 = representations.get(i);
62                FilterRepresentation r2 = step.representations.get(i);
63                if (!r1.equals(r2)) {
64                    return false;
65                }
66            }
67            return true;
68        }
69
70        public static Vector<CacheStep> buildSteps(Vector<FilterRepresentation> filters) {
71            Vector<CacheStep> steps = new Vector<CacheStep>();
72            CacheStep step = new CacheStep();
73            for (int i = 0; i < filters.size(); i++) {
74                FilterRepresentation representation = filters.elementAt(i);
75                if (step.canMergeWith(representation)) {
76                    step.add(representation.copy());
77                } else {
78                    steps.add(step);
79                    step = new CacheStep();
80                    step.add(representation.copy());
81                }
82            }
83            steps.add(step);
84            return steps;
85        }
86
87        public String getName() {
88            if (representations.size() > 0) {
89                return representations.get(0).getName();
90            }
91            return "EMPTY";
92        }
93
94        public Bitmap apply(FilterEnvironment environment, Bitmap cacheBitmap) {
95            boolean onlyGeometry = true;
96            Bitmap source = cacheBitmap;
97            for (FilterRepresentation representation : representations) {
98                if (representation.getFilterType() != FilterRepresentation.TYPE_GEOMETRY) {
99                    onlyGeometry = false;
100                    break;
101                }
102            }
103            if (onlyGeometry) {
104                ArrayList<FilterRepresentation> geometry = new ArrayList<FilterRepresentation>();
105                for (FilterRepresentation representation : representations) {
106                    geometry.add(representation);
107                }
108                if (DEBUG) {
109                    Log.v(LOGTAG, "Apply geometry to bitmap " + cacheBitmap);
110                }
111                cacheBitmap = GeometryMathUtils.applyGeometryRepresentations(geometry, cacheBitmap);
112            } else {
113                for (FilterRepresentation representation : representations) {
114                    if (DEBUG) {
115                        Log.v(LOGTAG, "Apply " + representation.getSerializationName()
116                                + " to bitmap " + cacheBitmap);
117                    }
118                    cacheBitmap = environment.applyRepresentation(representation, cacheBitmap);
119                }
120            }
121            if (cacheBitmap != source) {
122                environment.cache(source);
123            }
124            if (DEBUG) {
125                Log.v(LOGTAG, "Apply returns bitmap " + cacheBitmap);
126            }
127            return cacheBitmap;
128        }
129    }
130
131    public Bitmap process(Bitmap originalBitmap,
132                          Vector<FilterRepresentation> filters,
133                          FilterEnvironment environment) {
134
135        if (filters.size() == 0) {
136            return environment.getBitmapCopy(originalBitmap, BitmapCache.PREVIEW_CACHE_NO_FILTERS);
137        }
138
139        environment.getBimapCache().setCacheProcessing(this);
140
141        if (DEBUG) {
142            displayFilters(filters);
143        }
144        Vector<CacheStep> steps = CacheStep.buildSteps(filters);
145        // New set of filters, let's clear the cache and rebuild it.
146        if (steps.size() != mSteps.size()) {
147            mSteps = steps;
148        }
149
150        if (DEBUG) {
151            displaySteps(mSteps);
152        }
153
154        // First, let's find how similar we are in our cache
155        // compared to the current list of filters
156        int similarUpToIndex = -1;
157        boolean similar = true;
158        for (int i = 0; i < steps.size(); i++) {
159            CacheStep newStep = steps.elementAt(i);
160            CacheStep cacheStep = mSteps.elementAt(i);
161            if (similar) {
162                similar = newStep.equals(cacheStep);
163            }
164            if (similar) {
165                similarUpToIndex = i;
166            } else {
167                mSteps.remove(i);
168                mSteps.insertElementAt(newStep, i);
169                environment.cache(cacheStep.cache);
170            }
171        }
172        if (DEBUG) {
173            Log.v(LOGTAG, "similar up to index " + similarUpToIndex);
174        }
175
176        // Now, let's get the earliest cached result in our pipeline
177        Bitmap cacheBitmap = null;
178        int findBaseImageIndex = similarUpToIndex;
179        if (findBaseImageIndex > -1) {
180            while (findBaseImageIndex > 0
181                    && mSteps.elementAt(findBaseImageIndex).cache == null) {
182                findBaseImageIndex--;
183            }
184            cacheBitmap = mSteps.elementAt(findBaseImageIndex).cache;
185        }
186
187        if (DEBUG) {
188            Log.v(LOGTAG, "found baseImageIndex: " + findBaseImageIndex + " max is "
189                    + mSteps.size() + " cacheBitmap: " + cacheBitmap);
190        }
191
192        if (NO_CACHING) {
193            cacheBitmap = environment.getBitmapCopy(originalBitmap,
194                    BitmapCache.PREVIEW_CACHE_NO_ROOT);
195            for (int i = 0; i < mSteps.size(); i++) {
196                CacheStep step = mSteps.elementAt(i);
197                Bitmap prev = cacheBitmap;
198                cacheBitmap = step.apply(environment, cacheBitmap);
199                if (prev != cacheBitmap) {
200                    environment.cache(prev);
201                }
202            }
203            return cacheBitmap;
204        }
205
206        Bitmap originalCopy = null;
207        int lastPositionCached = -1;
208        for (int i = findBaseImageIndex; i < mSteps.size(); i++) {
209            if (i == -1 || cacheBitmap == null) {
210                cacheBitmap = environment.getBitmapCopy(originalBitmap,
211                        BitmapCache.PREVIEW_CACHE_NO_ROOT);
212                originalCopy = cacheBitmap;
213                if (DEBUG) {
214                    Log.v(LOGTAG, "i: " + i + " cacheBitmap: " + cacheBitmap + " w: "
215                            + cacheBitmap.getWidth() + " h: " + cacheBitmap.getHeight()
216                            + " got from original Bitmap: " + originalBitmap + " w: "
217                            + originalBitmap.getWidth() + " h: " + originalBitmap.getHeight());
218                }
219                if (i == -1) {
220                    continue;
221                }
222            }
223            CacheStep step = mSteps.elementAt(i);
224            if (step.cache == null) {
225                if (DEBUG) {
226                    Log.v(LOGTAG, "i: " + i + " get new copy for cacheBitmap "
227                            + cacheBitmap + " apply...");
228                }
229                cacheBitmap = environment.getBitmapCopy(cacheBitmap, BitmapCache.PREVIEW_CACHE);
230                cacheBitmap = step.apply(environment, cacheBitmap);
231                step.cache = cacheBitmap;
232                lastPositionCached = i;
233            }
234        }
235        environment.cache(originalCopy);
236
237        if (DEBUG) {
238            Log.v(LOGTAG, "now let's cleanup the cache...");
239            displayNbBitmapsInCache();
240        }
241
242        // Let's see if we can cleanup the cache for unused bitmaps
243        for (int i = 0; i < similarUpToIndex; i++) {
244            CacheStep currentStep = mSteps.elementAt(i);
245            Bitmap bitmap = currentStep.cache;
246            currentStep.cache = null;
247            environment.cache(bitmap);
248        }
249
250        if (DEBUG) {
251            Log.v(LOGTAG, "cleanup done...");
252            displayNbBitmapsInCache();
253        }
254        if (lastPositionCached != -1) {
255            // The last element will never be reused, remove it from the cache.
256            mSteps.elementAt(lastPositionCached).cache = null;
257        }
258        if (contains(cacheBitmap)) {
259            return environment.getBitmapCopy(cacheBitmap, BitmapCache.PREVIEW_CACHE_NO_APPLY);
260        }
261        return cacheBitmap;
262    }
263
264    public boolean contains(Bitmap bitmap) {
265        for (int i = 0; i < mSteps.size(); i++) {
266            if (mSteps.elementAt(i).cache == bitmap) {
267                return true;
268            }
269        }
270        return false;
271    }
272
273    private void displayFilters(Vector<FilterRepresentation> filters) {
274        Log.v(LOGTAG, "------>>> Filters received");
275        for (int i = 0; i < filters.size(); i++) {
276            FilterRepresentation filter = filters.elementAt(i);
277            Log.v(LOGTAG, "[" + i + "] - " + filter.getName());
278        }
279        Log.v(LOGTAG, "<<<------");
280    }
281
282    private void displaySteps(Vector<CacheStep> filters) {
283        Log.v(LOGTAG, "------>>>");
284        for (int i = 0; i < filters.size(); i++) {
285            CacheStep newStep = filters.elementAt(i);
286            CacheStep step = mSteps.elementAt(i);
287            boolean similar = step.equals(newStep);
288            Log.v(LOGTAG, "[" + i + "] - " + step.getName()
289                    + " similar rep ? " + (similar ? "YES" : "NO")
290                    + " -- bitmap: " + step.cache);
291        }
292        Log.v(LOGTAG, "<<<------");
293    }
294
295    private void displayNbBitmapsInCache() {
296        int nbBitmapsCached = 0;
297        for (int i = 0; i < mSteps.size(); i++) {
298            CacheStep step = mSteps.elementAt(i);
299            if (step.cache != null) {
300                nbBitmapsCached++;
301            }
302        }
303        Log.v(LOGTAG, "nb bitmaps in cache: " + nbBitmapsCached + " / " + mSteps.size());
304    }
305
306}
307