Home | History | Annotate | Download | only in imagepixelization
      1 /*
      2  * Copyright (C) 2013 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 package com.example.android.imagepixelization;
     17 
     18 import android.animation.ObjectAnimator;
     19 import android.app.Activity;
     20 import android.graphics.Bitmap;
     21 import android.graphics.BitmapFactory;
     22 import android.graphics.Color;
     23 import android.graphics.drawable.BitmapDrawable;
     24 import android.os.AsyncTask;
     25 import android.os.Build;
     26 import android.os.Bundle;
     27 import android.view.Menu;
     28 import android.view.MenuItem;
     29 import android.view.animation.LinearInterpolator;
     30 import android.widget.ImageView;
     31 import android.widget.SeekBar;
     32 
     33 import java.util.Arrays;
     34 
     35 /**
     36  * This application shows three different graphics/animation concepts.
     37  *
     38  * A pixelization effect is applied to an image with varying pixelization
     39  * factors to achieve an image that is pixelized to varying degrees. In
     40  * order to optimize the amount of image processing performed on the image
     41  * being pixelized, the pixelization effect only takes place if a predefined
     42  * amount of time has elapsed since the main image was last pixelized. The
     43  * effect is also applied when the user stops moving the seekbar.
     44  *
     45  * This application also shows how to use a ValueAnimator to achieve a
     46  * smooth self-animating seekbar.
     47  *
     48  * Lastly, this application shows a use case of AsyncTask where some
     49  * computation heavy processing can be moved onto a background thread,
     50  * so as to keep the UI completely responsive to user input.
     51  */
     52 public class ImagePixelization extends Activity {
     53 
     54     final private static int SEEKBAR_ANIMATION_DURATION = 10000;
     55     final private static int TIME_BETWEEN_TASKS = 400;
     56     final private static int SEEKBAR_STOP_CHANGE_DELTA = 5;
     57     final private static float PROGRESS_TO_PIXELIZATION_FACTOR = 4000.0f;
     58 
     59     Bitmap mImageBitmap;
     60     ImageView mImageView;
     61     SeekBar mSeekBar;
     62     boolean mIsChecked = false;
     63     boolean mIsBuiltinPixelizationChecked = false;
     64     int mLastProgress = 0;
     65     long mLastTime = 0;
     66     Bitmap mPixelatedBitmap;
     67 
     68     @Override
     69     protected void onCreate(Bundle savedInstanceState) {
     70         super.onCreate(savedInstanceState);
     71         setContentView(R.layout.activity_image_pixelization);
     72 
     73         mImageView = (ImageView) findViewById(R.id.pixelView);
     74         mSeekBar = (SeekBar)findViewById(R.id.seekbar);
     75 
     76         mImageBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
     77         mImageView.setImageBitmap(mImageBitmap);
     78 
     79         mSeekBar.setOnSeekBarChangeListener(mOnSeekBarChangeListener);
     80     }
     81 
     82     private SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener =
     83             new SeekBar.OnSeekBarChangeListener() {
     84 
     85         @Override
     86         public void onStopTrackingTouch(SeekBar seekBar) {
     87             if (Math.abs(mSeekBar.getProgress() - mLastProgress) > SEEKBAR_STOP_CHANGE_DELTA) {
     88                 invokePixelization();
     89             }
     90         }
     91 
     92         @Override
     93         public void onStartTrackingTouch(SeekBar seekBar) {
     94         }
     95 
     96         @Override
     97         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
     98             checkIfShouldPixelize();
     99         }
    100     };
    101 
    102     /**
    103      * Checks if enough time has elapsed since the last pixelization call was invoked.
    104      * This prevents too many pixelization processes from being invoked at the same time
    105      * while previous ones have not yet completed.
    106      */
    107     public void checkIfShouldPixelize() {
    108         if ((System.currentTimeMillis() - mLastTime) > TIME_BETWEEN_TASKS) {
    109             invokePixelization();
    110         }
    111     }
    112 
    113     @Override
    114     public boolean onCreateOptionsMenu(Menu menu) {
    115         getMenuInflater().inflate(R.menu.image_pixelization, menu);
    116         return true;
    117     }
    118 
    119     @Override
    120     public boolean onOptionsItemSelected (MenuItem item) {
    121         switch (item.getItemId()){
    122             case R.id.animate:
    123                 ObjectAnimator animator = ObjectAnimator.ofInt(mSeekBar, "progress", 0,
    124                         mSeekBar.getMax());
    125                 animator.setInterpolator(new LinearInterpolator());
    126                 animator.setDuration(SEEKBAR_ANIMATION_DURATION);
    127                 animator.start();
    128                 break;
    129             case R.id.checkbox:
    130                 if (mIsChecked) {
    131                     item.setChecked(false);
    132                     mIsChecked = false;
    133                 } else {
    134                     item.setChecked(true);
    135                     mIsChecked = true;
    136                 }
    137                 break;
    138             case R.id.builtin_pixelation_checkbox:
    139                 mIsBuiltinPixelizationChecked = !mIsBuiltinPixelizationChecked;
    140                 item.setChecked(mIsBuiltinPixelizationChecked);
    141                 break;
    142             default:
    143                 break;
    144         }
    145         return true;
    146     }
    147 
    148     /**
    149      * A simple pixelization algorithm. This uses a box blur algorithm where all the
    150      * pixels within some region are averaged, and that average pixel value is then
    151      * applied to all the pixels within that region. A higher pixelization factor
    152      * imposes a smaller number of regions of greater size. Similarly, a smaller
    153      * pixelization factor imposes a larger number of regions of smaller size.
    154      */
    155     public BitmapDrawable customImagePixelization(float pixelizationFactor, Bitmap bitmap) {
    156 
    157         int width = bitmap.getWidth();
    158         int height = bitmap.getHeight();
    159 
    160         if (mPixelatedBitmap == null || !(width == mPixelatedBitmap.getWidth() && height ==
    161                 mPixelatedBitmap.getHeight())) {
    162             mPixelatedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    163         }
    164 
    165         int xPixels = (int) (pixelizationFactor * ((float)width));
    166         xPixels = xPixels > 0 ? xPixels : 1;
    167         int yPixels = (int)  (pixelizationFactor * ((float)height));
    168         yPixels = yPixels > 0 ? yPixels : 1;
    169         int pixel = 0, red = 0, green = 0, blue = 0, numPixels = 0;
    170 
    171         int[] bitmapPixels = new int[width * height];
    172         bitmap.getPixels(bitmapPixels, 0, width, 0, 0, width, height);
    173 
    174         int[] pixels = new int[yPixels * xPixels];
    175 
    176         int maxX, maxY;
    177 
    178         for (int y = 0; y < height; y+=yPixels) {
    179             for (int x = 0; x < width; x+=xPixels) {
    180 
    181                 numPixels = red = green = blue = 0;
    182 
    183                 maxX = Math.min(x + xPixels, width);
    184                 maxY = Math.min(y + yPixels, height);
    185 
    186                 for (int i = x; i < maxX; i++) {
    187                     for (int j = y; j < maxY; j++) {
    188                         pixel = bitmapPixels[j * width + i];
    189                         red += Color.red(pixel);
    190                         green += Color.green(pixel);
    191                         blue += Color.blue(pixel);
    192                         numPixels ++;
    193                     }
    194                 }
    195 
    196                 pixel = Color.rgb(red / numPixels, green / numPixels, blue / numPixels);
    197 
    198                 Arrays.fill(pixels, pixel);
    199 
    200                 int w = Math.min(xPixels, width - x);
    201                 int h = Math.min(yPixels, height - y);
    202 
    203                 mPixelatedBitmap.setPixels(pixels, 0 , w, x , y, w, h);
    204             }
    205         }
    206 
    207         return new BitmapDrawable(getResources(), mPixelatedBitmap);
    208     }
    209 
    210     /**
    211      * This method of image pixelization utilizes the bitmap scaling operations built
    212      * into the framework. By downscaling the bitmap and upscaling it back to its
    213      * original size (while setting the filter flag to false), the same effect can be
    214      * achieved with much better performance.
    215      */
    216     public BitmapDrawable builtInPixelization(float pixelizationFactor, Bitmap bitmap) {
    217 
    218         int width = bitmap.getWidth();
    219         int height = bitmap.getHeight();
    220 
    221         int downScaleFactorWidth = (int)(pixelizationFactor * width);
    222         downScaleFactorWidth = downScaleFactorWidth > 0 ? downScaleFactorWidth : 1;
    223         int downScaleFactorHeight = (int)(pixelizationFactor * height);
    224         downScaleFactorHeight = downScaleFactorHeight > 0 ? downScaleFactorHeight : 1;
    225 
    226         int downScaledWidth =  width / downScaleFactorWidth;
    227         int downScaledHeight = height / downScaleFactorHeight;
    228 
    229         Bitmap pixelatedBitmap = Bitmap.createScaledBitmap(bitmap, downScaledWidth,
    230                 downScaledHeight, false);
    231 
    232         /* Bitmap's createScaledBitmap method has a filter parameter that can be set to either
    233          * true or false in order to specify either bilinear filtering or point sampling
    234          * respectively when the bitmap is scaled up or now.
    235          *
    236          * Similarly, a BitmapDrawable also has a flag to specify the same thing. When the
    237          * BitmapDrawable is applied to an ImageView that has some scaleType, the filtering
    238          * flag is taken into consideration. However, for optimization purposes, this flag was
    239          * ignored in BitmapDrawables before Jelly Bean MR1.
    240          *
    241          * Here, it is important to note that prior to JBMR1, two bitmap scaling operations
    242          * are required to achieve the pixelization effect. Otherwise, a BitmapDrawable
    243          * can be created corresponding to the downscaled bitmap such that when it is
    244          * upscaled to fit the ImageView, the upscaling operation is a lot faster since
    245          * it uses internal optimizations to fit the ImageView.
    246          * */
    247         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
    248             BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), pixelatedBitmap);
    249             bitmapDrawable.setFilterBitmap(false);
    250             return bitmapDrawable;
    251         } else {
    252             Bitmap upscaled = Bitmap.createScaledBitmap(pixelatedBitmap, width, height, false);
    253             return new BitmapDrawable(getResources(), upscaled);
    254         }
    255     }
    256 
    257     /**
    258      * Invokes pixelization either on the main thread or on a background thread
    259      * depending on whether or not the checkbox was checked.
    260      */
    261     public void invokePixelization () {
    262         mLastTime = System.currentTimeMillis();
    263         mLastProgress = mSeekBar.getProgress();
    264         if (mIsChecked) {
    265             PixelizeImageAsyncTask asyncPixelateTask = new PixelizeImageAsyncTask();
    266             asyncPixelateTask.execute(mSeekBar.getProgress() / PROGRESS_TO_PIXELIZATION_FACTOR,
    267                     mImageBitmap);
    268         } else {
    269             mImageView.setImageDrawable(pixelizeImage(mSeekBar.getProgress()
    270                     / PROGRESS_TO_PIXELIZATION_FACTOR, mImageBitmap));
    271         }
    272     }
    273 
    274     /**
    275      *  Selects either the custom pixelization algorithm that sets and gets bitmap
    276      *  pixels manually or the one that uses built-in bitmap operations.
    277      */
    278     public BitmapDrawable pixelizeImage(float pixelizationFactor, Bitmap bitmap) {
    279         if (mIsBuiltinPixelizationChecked) {
    280             return builtInPixelization(pixelizationFactor, bitmap);
    281         } else {
    282             return customImagePixelization(pixelizationFactor, bitmap);
    283         }
    284     }
    285 
    286     /**
    287      * Implementation of the AsyncTask class showing how to run the
    288      * pixelization algorithm in the background, and retrieving the
    289      * pixelated image from the resulting operation.
    290      */
    291     private class PixelizeImageAsyncTask extends AsyncTask<Object, Void, BitmapDrawable> {
    292 
    293         @Override
    294         protected BitmapDrawable doInBackground(Object... params) {
    295             float pixelizationFactor = (Float)params[0];
    296             Bitmap originalBitmap = (Bitmap)params[1];
    297             return pixelizeImage(pixelizationFactor, originalBitmap);
    298         }
    299 
    300         @Override
    301         protected void onPostExecute(BitmapDrawable result) {
    302             mImageView.setImageDrawable(result);
    303         }
    304 
    305         @Override
    306         protected void onPreExecute() {
    307 
    308         }
    309 
    310         @Override
    311         protected void onProgressUpdate(Void... values) {
    312 
    313         }
    314     }
    315 }