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 }