1 /*
2  * Copyright (C) 2014 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.example.android.renderscriptintrinsic;
18 
19 import android.app.Activity;
20 import android.graphics.Bitmap;
21 import android.graphics.BitmapFactory;
22 import android.os.AsyncTask;
23 import android.os.Bundle;
24 import android.widget.CompoundButton;
25 import android.widget.CompoundButton.OnCheckedChangeListener;
26 import android.widget.ImageView;
27 import android.widget.RadioButton;
28 import android.widget.SeekBar;
29 import android.widget.SeekBar.OnSeekBarChangeListener;
30 import android.support.v8.renderscript.*;
31 
32 public class MainActivity extends Activity {
33     /* Number of bitmaps that is used for renderScript thread and UI thread synchronization.
34        Ideally, this can be reduced to 2, however in some devices, 2 buffers still showing tierings on UI.
35        Investigating a root cause.
36      */
37     private final int NUM_BITMAPS = 3;
38     private int mCurrentBitmap = 0;
39     private Bitmap mBitmapIn;
40     private Bitmap[] mBitmapsOut;
41     private ImageView mImageView;
42 
43     private RenderScript mRS;
44     private Allocation mInAllocation;
45     private Allocation[] mOutAllocations;
46 
47     private ScriptIntrinsicBlur mScriptBlur;
48     private ScriptIntrinsicConvolve5x5 mScriptConvolve;
49     private ScriptIntrinsicColorMatrix mScriptMatrix;
50 
51     private final int MODE_BLUR = 0;
52     private final int MODE_CONVOLVE = 1;
53     private final int MODE_COLORMATRIX = 2;
54 
55     private int mFilterMode = MODE_BLUR;
56 
57     private RenderScriptTask mLatestTask = null;
58 
59     @Override
onCreate(Bundle savedInstanceState)60     protected void onCreate(Bundle savedInstanceState) {
61         super.onCreate(savedInstanceState);
62 
63         setContentView(R.layout.main_layout);
64 
65         /*
66          * Initialize UI
67          */
68 
69         //Set up main image view
70         mBitmapIn = loadBitmap(R.drawable.data);
71         mBitmapsOut = new Bitmap[NUM_BITMAPS];
72         for (int i = 0; i < NUM_BITMAPS; ++i) {
73             mBitmapsOut[i] = Bitmap.createBitmap(mBitmapIn.getWidth(),
74                     mBitmapIn.getHeight(), mBitmapIn.getConfig());
75         }
76 
77         mImageView = (ImageView) findViewById(R.id.imageView);
78         mImageView.setImageBitmap(mBitmapsOut[mCurrentBitmap]);
79         mCurrentBitmap += (mCurrentBitmap + 1) % NUM_BITMAPS;
80 
81         //Set up seekbar
82         final SeekBar seekbar = (SeekBar) findViewById(R.id.seekBar1);
83         seekbar.setProgress(50);
84         seekbar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
85             public void onProgressChanged(SeekBar seekBar, int progress,
86                                           boolean fromUser) {
87                 updateImage(progress);
88             }
89 
90             @Override
91             public void onStartTrackingTouch(SeekBar seekBar) {
92             }
93 
94             @Override
95             public void onStopTrackingTouch(SeekBar seekBar) {
96             }
97         });
98 
99         //Setup effect selector
100         RadioButton radio0 = (RadioButton) findViewById(R.id.radio0);
101         radio0.setOnCheckedChangeListener(new OnCheckedChangeListener() {
102 
103             @Override
104             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
105                 if (isChecked) {
106                     mFilterMode = MODE_BLUR;
107                     updateImage(seekbar.getProgress());
108                 }
109             }
110         });
111         RadioButton radio1 = (RadioButton) findViewById(R.id.radio1);
112         radio1.setOnCheckedChangeListener(new OnCheckedChangeListener() {
113 
114             @Override
115             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
116                 if (isChecked) {
117                     mFilterMode = MODE_CONVOLVE;
118                     updateImage(seekbar.getProgress());
119                 }
120             }
121         });
122         RadioButton radio2 = (RadioButton) findViewById(R.id.radio2);
123         radio2.setOnCheckedChangeListener(new OnCheckedChangeListener() {
124 
125             @Override
126             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
127                 if (isChecked) {
128                     mFilterMode = MODE_COLORMATRIX;
129                     updateImage(seekbar.getProgress());
130                 }
131             }
132         });
133 
134         /*
135          * Create renderScript
136          */
137         createScript();
138 
139         /*
140          * Create thumbnails
141          */
142         createThumbnail();
143 
144 
145         /*
146          * Invoke renderScript kernel and update imageView
147          */
148         mFilterMode = MODE_BLUR;
149         updateImage(50);
150     }
151 
createScript()152     private void createScript() {
153         mRS = RenderScript.create(this);
154 
155         mInAllocation = Allocation.createFromBitmap(mRS, mBitmapIn);
156 
157         mOutAllocations = new Allocation[NUM_BITMAPS];
158         for (int i = 0; i < NUM_BITMAPS; ++i) {
159             mOutAllocations[i] = Allocation.createFromBitmap(mRS, mBitmapsOut[i]);
160         }
161 
162         /*
163         Create intrinsics.
164         RenderScript has built-in features such as blur, convolve filter etc.
165         These intrinsics are handy for specific operations without writing RenderScript kernel.
166         In the sample, it's creating blur, convolve and matrix intrinsics.
167          */
168 
169         mScriptBlur = ScriptIntrinsicBlur.create(mRS, Element.U8_4(mRS));
170         mScriptConvolve = ScriptIntrinsicConvolve5x5.create(mRS,
171                 Element.U8_4(mRS));
172         mScriptMatrix = ScriptIntrinsicColorMatrix.create(mRS,
173                 Element.U8_4(mRS));
174     }
175 
performFilter(Allocation inAllocation, Allocation outAllocation, Bitmap bitmapOut, float value)176     private void performFilter(Allocation inAllocation,
177                                Allocation outAllocation, Bitmap bitmapOut, float value) {
178         switch (mFilterMode) {
179             case MODE_BLUR:
180             /*
181              * Set blur kernel size
182              */
183                 mScriptBlur.setRadius(value);
184 
185             /*
186              * Invoke filter kernel
187              */
188                 mScriptBlur.setInput(inAllocation);
189                 mScriptBlur.forEach(outAllocation);
190                 break;
191             case MODE_CONVOLVE: {
192                 float f1 = value;
193                 float f2 = 1.0f - f1;
194 
195                 // Emboss filter kernel
196                 float coefficients[] = {-f1 * 2, 0, -f1, 0, 0, 0, -f2 * 2, -f2, 0,
197                         0, -f1, -f2, 1, f2, f1, 0, 0, f2, f2 * 2, 0, 0, 0, f1, 0,
198                         f1 * 2,};
199             /*
200              * Set kernel parameter
201              */
202                 mScriptConvolve.setCoefficients(coefficients);
203 
204             /*
205              * Invoke filter kernel
206              */
207                 mScriptConvolve.setInput(inAllocation);
208                 mScriptConvolve.forEach(outAllocation);
209                 break;
210             }
211             case MODE_COLORMATRIX: {
212             /*
213              * Set HUE rotation matrix
214              * The matrix below performs a combined operation of,
215              * RGB->HSV transform * HUE rotation * HSV->RGB transform
216              */
217                 float cos = (float) Math.cos((double) value);
218                 float sin = (float) Math.sin((double) value);
219                 Matrix3f mat = new Matrix3f();
220                 mat.set(0, 0, (float) (.299 + .701 * cos + .168 * sin));
221                 mat.set(1, 0, (float) (.587 - .587 * cos + .330 * sin));
222                 mat.set(2, 0, (float) (.114 - .114 * cos - .497 * sin));
223                 mat.set(0, 1, (float) (.299 - .299 * cos - .328 * sin));
224                 mat.set(1, 1, (float) (.587 + .413 * cos + .035 * sin));
225                 mat.set(2, 1, (float) (.114 - .114 * cos + .292 * sin));
226                 mat.set(0, 2, (float) (.299 - .3 * cos + 1.25 * sin));
227                 mat.set(1, 2, (float) (.587 - .588 * cos - 1.05 * sin));
228                 mat.set(2, 2, (float) (.114 + .886 * cos - .203 * sin));
229                 mScriptMatrix.setColorMatrix(mat);
230 
231             /*
232              * Invoke filter kernel
233              */
234                 mScriptMatrix.forEach(inAllocation, outAllocation);
235             }
236             break;
237         }
238 
239         /*
240          * Copy to bitmap and invalidate image view
241          */
242         outAllocation.copyTo(bitmapOut);
243     }
244 
245     /*
246     Convert seekBar progress parameter (0-100 in range) to parameter for each intrinsic filter.
247     (e.g. 1.0-25.0 in Blur filter)
248      */
getFilterParameter(int i)249     private float getFilterParameter(int i) {
250         float f = 0.f;
251         switch (mFilterMode) {
252             case MODE_BLUR: {
253                 final float max = 25.0f;
254                 final float min = 1.f;
255                 f = (float) ((max - min) * (i / 100.0) + min);
256             }
257             break;
258             case MODE_CONVOLVE: {
259                 final float max = 2.f;
260                 final float min = 0.f;
261                 f = (float) ((max - min) * (i / 100.0) + min);
262             }
263             break;
264             case MODE_COLORMATRIX: {
265                 final float max = (float) Math.PI;
266                 final float min = (float) -Math.PI;
267                 f = (float) ((max - min) * (i / 100.0) + min);
268             }
269             break;
270         }
271         return f;
272 
273     }
274 
275     /*
276      * In the AsyncTask, it invokes RenderScript intrinsics to do a filtering.
277      * After the filtering is done, an operation blocks at Allication.copyTo() in AsyncTask thread.
278      * Once all operation is finished at onPostExecute() in UI thread, it can invalidate and update ImageView UI.
279      */
280     private class RenderScriptTask extends AsyncTask<Float, Integer, Integer> {
281         Boolean issued = false;
282 
doInBackground(Float... values)283         protected Integer doInBackground(Float... values) {
284             int index = -1;
285             if (isCancelled() == false) {
286                 issued = true;
287                 index = mCurrentBitmap;
288 
289                 performFilter(mInAllocation, mOutAllocations[index], mBitmapsOut[index], values[0]);
290                 mCurrentBitmap = (mCurrentBitmap + 1) % NUM_BITMAPS;
291             }
292             return index;
293         }
294 
updateView(Integer result)295         void updateView(Integer result) {
296             if (result != -1) {
297                 // Request UI update
298                 mImageView.setImageBitmap(mBitmapsOut[result]);
299                 mImageView.invalidate();
300             }
301         }
302 
onPostExecute(Integer result)303         protected void onPostExecute(Integer result) {
304             updateView(result);
305         }
306 
onCancelled(Integer result)307         protected void onCancelled(Integer result) {
308             if (issued) {
309                 updateView(result);
310             }
311         }
312     }
313 
314     /*
315     Invoke AsynchTask and cancel previous task.
316     When AsyncTasks are piled up (typically in slow device with heavy kernel),
317     Only the latest (and already started) task invokes RenderScript operation.
318      */
updateImage(int progress)319     private void updateImage(int progress) {
320         float f = getFilterParameter(progress);
321 
322         if (mLatestTask != null)
323             mLatestTask.cancel(false);
324         mLatestTask = new RenderScriptTask();
325 
326         mLatestTask.execute(f);
327     }
328 
329     /*
330     Helper to load Bitmap from resource
331      */
loadBitmap(int resource)332     private Bitmap loadBitmap(int resource) {
333         final BitmapFactory.Options options = new BitmapFactory.Options();
334         options.inPreferredConfig = Bitmap.Config.ARGB_8888;
335         return BitmapFactory.decodeResource(getResources(), resource, options);
336     }
337 
338     /*
339     Create thumbNail for UI. It invokes RenderScript kernel synchronously in UI-thread,
340     which is OK for small thumbnail (but not ideal).
341      */
createThumbnail()342     private void createThumbnail() {
343         int width = 72;
344         int height = 96;
345         float scale = getResources().getDisplayMetrics().density;
346         int pixelsWidth = (int) (width * scale + 0.5f);
347         int pixelsHeight = (int) (height * scale + 0.5f);
348 
349         //Temporary image
350         Bitmap tempBitmap = Bitmap.createScaledBitmap(mBitmapIn, pixelsWidth, pixelsHeight, false);
351         Allocation inAllocation = Allocation.createFromBitmap(mRS, tempBitmap);
352 
353         //Create thumbnail with each RS intrinsic and set it to radio buttons
354         int[] modes = {MODE_BLUR, MODE_CONVOLVE, MODE_COLORMATRIX};
355         int[] ids = {R.id.radio0, R.id.radio1, R.id.radio2};
356         int[] parameter = {50, 100, 25};
357         for (int mode : modes) {
358             mFilterMode = mode;
359             float f = getFilterParameter(parameter[mode]);
360 
361             Bitmap destBitpmap = Bitmap.createBitmap(tempBitmap.getWidth(),
362                     tempBitmap.getHeight(), tempBitmap.getConfig());
363             Allocation outAllocation = Allocation.createFromBitmap(mRS, destBitpmap);
364             performFilter(inAllocation, outAllocation, destBitpmap, f);
365 
366             ThumbnailRadioButton button = (ThumbnailRadioButton) findViewById(ids[mode]);
367             button.setThumbnail(destBitpmap);
368         }
369     }
370 }
371