blob: 4b6f5ce16a9aa0bb4eec2c486e057865ca61a10f [file] [log] [blame]
Alexander Lucas0b3758e2014-02-06 15:38:51 -08001/*
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
17package com.example.android.renderscriptintrinsic;
18
19import android.app.Activity;
20import android.graphics.Bitmap;
21import android.graphics.BitmapFactory;
22import android.os.AsyncTask;
23import android.os.Bundle;
24import android.widget.CompoundButton;
25import android.widget.CompoundButton.OnCheckedChangeListener;
26import android.widget.ImageView;
27import android.widget.RadioButton;
28import android.widget.SeekBar;
29import android.widget.SeekBar.OnSeekBarChangeListener;
30import android.support.v8.renderscript.*;
31
32public 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
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
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
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 */
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
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
295 void updateView(Integer result) {
296 if (result != -1) {
297 // Request UI update
298 mImageView.setImageBitmap(mBitmapsOut[result]);
299 mImageView.invalidate();
300 }
301 }
302
303 protected void onPostExecute(Integer result) {
304 updateView(result);
305 }
306
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 */
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 */
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 */
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}