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
onCreate(Bundle savedInstanceState)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      */
checkIfShouldPixelize()107     public void checkIfShouldPixelize() {
108         if ((System.currentTimeMillis() - mLastTime) > TIME_BETWEEN_TASKS) {
109             invokePixelization();
110         }
111     }
112 
113     @Override
onCreateOptionsMenu(Menu menu)114     public boolean onCreateOptionsMenu(Menu menu) {
115         getMenuInflater().inflate(R.menu.image_pixelization, menu);
116         return true;
117     }
118 
119     @Override
onOptionsItemSelected(MenuItem item)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      */
customImagePixelization(float pixelizationFactor, Bitmap bitmap)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      */
builtInPixelization(float pixelizationFactor, Bitmap bitmap)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      */
invokePixelization()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      */
pixelizeImage(float pixelizationFactor, Bitmap bitmap)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
doInBackground(Object... params)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
onPostExecute(BitmapDrawable result)301         protected void onPostExecute(BitmapDrawable result) {
302             mImageView.setImageDrawable(result);
303         }
304 
305         @Override
onPreExecute()306         protected void onPreExecute() {
307 
308         }
309 
310         @Override
onProgressUpdate(Void... values)311         protected void onProgressUpdate(Void... values) {
312 
313         }
314     }
315 }