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