Home | History | Annotate | Download | only in com.example.android.renderscriptintrinsic
      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
     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 }
    371