1/*
2 * Copyright (C) 2012 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.filters;
18
19import android.graphics.Bitmap;
20import android.graphics.BitmapFactory;
21import android.support.v8.renderscript.*;
22import android.util.Log;
23import android.content.res.Resources;
24import com.android.gallery3d.R;
25import com.android.gallery3d.filtershow.pipeline.PipelineInterface;
26
27public abstract class ImageFilterRS extends ImageFilter {
28    private static final String LOGTAG = "ImageFilterRS";
29    private boolean DEBUG = false;
30    private int mLastInputWidth = 0;
31    private int mLastInputHeight = 0;
32    private long mLastTimeCalled;
33
34    public static boolean PERF_LOGGING = false;
35
36    private static ScriptC_grey mGreyConvert = null;
37    private static RenderScript mRScache = null;
38
39    private volatile boolean mResourcesLoaded = false;
40
41    protected abstract void createFilter(android.content.res.Resources res,
42            float scaleFactor, int quality);
43
44    protected void createFilter(android.content.res.Resources res,
45    float scaleFactor, int quality, Allocation in) {}
46    protected void bindScriptValues(Allocation in) {}
47
48    protected abstract void runFilter();
49
50    protected void update(Bitmap bitmap) {
51        getOutPixelsAllocation().copyTo(bitmap);
52    }
53
54    protected RenderScript getRenderScriptContext() {
55        PipelineInterface pipeline = getEnvironment().getPipeline();
56        return pipeline.getRSContext();
57    }
58
59    protected Allocation getInPixelsAllocation() {
60        PipelineInterface pipeline = getEnvironment().getPipeline();
61        return pipeline.getInPixelsAllocation();
62    }
63
64    protected Allocation getOutPixelsAllocation() {
65        PipelineInterface pipeline = getEnvironment().getPipeline();
66        return pipeline.getOutPixelsAllocation();
67    }
68
69    @Override
70    public void apply(Allocation in, Allocation out) {
71        long startOverAll = System.nanoTime();
72        if (PERF_LOGGING) {
73            long delay = (startOverAll - mLastTimeCalled) / 1000;
74            String msg = String.format("%s; image size %dx%d; ", getName(),
75                    in.getType().getX(), in.getType().getY());
76            msg += String.format("called after %.2f ms (%.2f FPS); ",
77                    delay / 1000.f, 1000000.f / delay);
78            Log.i(LOGTAG, msg);
79        }
80        mLastTimeCalled = startOverAll;
81        long startFilter = 0;
82        long endFilter = 0;
83        if (!mResourcesLoaded) {
84            PipelineInterface pipeline = getEnvironment().getPipeline();
85            createFilter(pipeline.getResources(), getEnvironment().getScaleFactor(),
86                    getEnvironment().getQuality(), in);
87            mResourcesLoaded = true;
88        }
89        startFilter = System.nanoTime();
90        bindScriptValues(in);
91        run(in, out);
92        if (PERF_LOGGING) {
93            getRenderScriptContext().finish();
94            endFilter = System.nanoTime();
95            long endOverAll = System.nanoTime();
96            String msg = String.format("%s; image size %dx%d; ", getName(),
97                    in.getType().getX(), in.getType().getY());
98            long timeOverAll = (endOverAll - startOverAll) / 1000;
99            long timeFilter = (endFilter - startFilter) / 1000;
100            msg += String.format("over all %.2f ms (%.2f FPS); ",
101                    timeOverAll / 1000.f, 1000000.f / timeOverAll);
102            msg += String.format("run filter %.2f ms (%.2f FPS)",
103                    timeFilter / 1000.f, 1000000.f / timeFilter);
104            Log.i(LOGTAG, msg);
105        }
106    }
107
108    protected void run(Allocation in, Allocation out) {}
109
110    @Override
111    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
112        if (bitmap == null || bitmap.getWidth() == 0 || bitmap.getHeight() == 0) {
113            return bitmap;
114        }
115        try {
116            PipelineInterface pipeline = getEnvironment().getPipeline();
117            if (DEBUG) {
118                Log.v(LOGTAG, "apply filter " + getName() + " in pipeline " + pipeline.getName());
119            }
120            Resources rsc = pipeline.getResources();
121            boolean sizeChanged = false;
122            if (getInPixelsAllocation() != null
123                    && ((getInPixelsAllocation().getType().getX() != mLastInputWidth)
124                    || (getInPixelsAllocation().getType().getY() != mLastInputHeight))) {
125                sizeChanged = true;
126            }
127            if (pipeline.prepareRenderscriptAllocations(bitmap)
128                    || !isResourcesLoaded() || sizeChanged) {
129                freeResources();
130                createFilter(rsc, scaleFactor, quality);
131                setResourcesLoaded(true);
132                mLastInputWidth = getInPixelsAllocation().getType().getX();
133                mLastInputHeight = getInPixelsAllocation().getType().getY();
134            }
135            bindScriptValues();
136            runFilter();
137            update(bitmap);
138            if (DEBUG) {
139                Log.v(LOGTAG, "DONE apply filter " + getName() + " in pipeline " + pipeline.getName());
140            }
141        } catch (android.renderscript.RSIllegalArgumentException e) {
142            Log.e(LOGTAG, "Illegal argument? " + e);
143        } catch (android.renderscript.RSRuntimeException e) {
144            Log.e(LOGTAG, "RS runtime exception ? " + e);
145        } catch (java.lang.OutOfMemoryError e) {
146            // Many of the renderscript filters allocated large (>16Mb resources) in order to apply.
147            System.gc();
148            displayLowMemoryToast();
149            Log.e(LOGTAG, "not enough memory for filter " + getName(), e);
150        }
151        return bitmap;
152    }
153
154    protected static Allocation convertBitmap(RenderScript RS, Bitmap bitmap) {
155        return Allocation.createFromBitmap(RS, bitmap,
156                Allocation.MipmapControl.MIPMAP_NONE,
157                Allocation.USAGE_SCRIPT | Allocation.USAGE_GRAPHICS_TEXTURE);
158    }
159
160    private static Allocation convertRGBAtoA(RenderScript RS, Bitmap bitmap) {
161        if (RS != mRScache || mGreyConvert == null) {
162            mGreyConvert = new ScriptC_grey(RS, RS.getApplicationContext().getResources(),
163                                            R.raw.grey);
164            mRScache = RS;
165        }
166
167        Type.Builder tb_a8 = new Type.Builder(RS, Element.A_8(RS));
168
169        Allocation bitmapTemp = convertBitmap(RS, bitmap);
170        if (bitmapTemp.getType().getElement().isCompatible(Element.A_8(RS))) {
171            return bitmapTemp;
172        }
173
174        tb_a8.setX(bitmapTemp.getType().getX());
175        tb_a8.setY(bitmapTemp.getType().getY());
176        Allocation bitmapAlloc = Allocation.createTyped(RS, tb_a8.create(),
177                                                        Allocation.MipmapControl.MIPMAP_NONE,
178                                                        Allocation.USAGE_SCRIPT | Allocation.USAGE_GRAPHICS_TEXTURE);
179        mGreyConvert.forEach_RGBAtoA(bitmapTemp, bitmapAlloc);
180        bitmapTemp.destroy();
181        return bitmapAlloc;
182    }
183
184    public Allocation loadScaledResourceAlpha(int resource, int inSampleSize) {
185        Resources res = getEnvironment().getPipeline().getResources();
186        final BitmapFactory.Options options = new BitmapFactory.Options();
187        options.inSampleSize      = inSampleSize;
188        Bitmap bitmap = BitmapFactory.decodeResource(
189                res,
190                resource, options);
191        Allocation ret = convertRGBAtoA(getRenderScriptContext(), bitmap);
192        bitmap.recycle();
193        return ret;
194    }
195
196    public Allocation loadScaledResourceAlpha(int resource, int w, int h, int inSampleSize) {
197        Resources res = getEnvironment().getPipeline().getResources();
198        final BitmapFactory.Options options = new BitmapFactory.Options();
199        options.inSampleSize      = inSampleSize;
200        Bitmap bitmap = BitmapFactory.decodeResource(
201                res,
202                resource, options);
203        Bitmap resizeBitmap = Bitmap.createScaledBitmap(bitmap, w, h, true);
204        Allocation ret = convertRGBAtoA(getRenderScriptContext(), resizeBitmap);
205        resizeBitmap.recycle();
206        bitmap.recycle();
207        return ret;
208    }
209
210    public Allocation loadResourceAlpha(int resource) {
211        return loadScaledResourceAlpha(resource, 1);
212    }
213
214    public Allocation loadResource(int resource) {
215        Resources res = getEnvironment().getPipeline().getResources();
216        final BitmapFactory.Options options = new BitmapFactory.Options();
217        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
218        Bitmap bitmap = BitmapFactory.decodeResource(
219                res,
220                resource, options);
221        Allocation ret = convertBitmap(getRenderScriptContext(), bitmap);
222        bitmap.recycle();
223        return ret;
224    }
225
226    private boolean isResourcesLoaded() {
227        return mResourcesLoaded;
228    }
229
230    private void setResourcesLoaded(boolean resourcesLoaded) {
231        mResourcesLoaded = resourcesLoaded;
232    }
233
234    /**
235     *  Bitmaps and RS Allocations should be cleared here
236     */
237    abstract protected void resetAllocations();
238
239    /**
240     * RS Script objects (and all other RS objects) should be cleared here
241     */
242    public abstract void resetScripts();
243
244    /**
245     * Scripts values should be bound here
246     */
247    abstract protected void bindScriptValues();
248
249    public void freeResources() {
250        if (!isResourcesLoaded()) {
251            return;
252        }
253        resetAllocations();
254        mLastInputWidth = 0;
255        mLastInputHeight = 0;
256        setResourcesLoaded(false);
257    }
258}
259