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