diff --git a/samples/devbytes/graphics/ImagePixelization/AndroidManifest.xml b/samples/devbytes/graphics/ImagePixelization/AndroidManifest.xml new file mode 100644 index 000000000..7bbb4f6e0 --- /dev/null +++ b/samples/devbytes/graphics/ImagePixelization/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/samples/devbytes/graphics/ImagePixelization/res/drawable-hdpi/ic_launcher.png b/samples/devbytes/graphics/ImagePixelization/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 000000000..96a442e5b Binary files /dev/null and b/samples/devbytes/graphics/ImagePixelization/res/drawable-hdpi/ic_launcher.png differ diff --git a/samples/devbytes/graphics/ImagePixelization/res/drawable-hdpi/image.jpg b/samples/devbytes/graphics/ImagePixelization/res/drawable-hdpi/image.jpg new file mode 100644 index 000000000..00bdbbafc Binary files /dev/null and b/samples/devbytes/graphics/ImagePixelization/res/drawable-hdpi/image.jpg differ diff --git a/samples/devbytes/graphics/ImagePixelization/res/drawable-ldpi/ic_launcher.png b/samples/devbytes/graphics/ImagePixelization/res/drawable-ldpi/ic_launcher.png new file mode 100644 index 000000000..99238729d Binary files /dev/null and b/samples/devbytes/graphics/ImagePixelization/res/drawable-ldpi/ic_launcher.png differ diff --git a/samples/devbytes/graphics/ImagePixelization/res/drawable-mdpi/ic_launcher.png b/samples/devbytes/graphics/ImagePixelization/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 000000000..359047dfa Binary files /dev/null and b/samples/devbytes/graphics/ImagePixelization/res/drawable-mdpi/ic_launcher.png differ diff --git a/samples/devbytes/graphics/ImagePixelization/res/drawable-xhdpi/ic_launcher.png b/samples/devbytes/graphics/ImagePixelization/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 000000000..71c6d760f Binary files /dev/null and b/samples/devbytes/graphics/ImagePixelization/res/drawable-xhdpi/ic_launcher.png differ diff --git a/samples/devbytes/graphics/ImagePixelization/res/layout/activity_image_pixelization.xml b/samples/devbytes/graphics/ImagePixelization/res/layout/activity_image_pixelization.xml new file mode 100644 index 000000000..b3e30a8cb --- /dev/null +++ b/samples/devbytes/graphics/ImagePixelization/res/layout/activity_image_pixelization.xml @@ -0,0 +1,42 @@ + + + + + + + + + diff --git a/samples/devbytes/graphics/ImagePixelization/res/menu/image_pixelization.xml b/samples/devbytes/graphics/ImagePixelization/res/menu/image_pixelization.xml new file mode 100644 index 000000000..e6a8d6693 --- /dev/null +++ b/samples/devbytes/graphics/ImagePixelization/res/menu/image_pixelization.xml @@ -0,0 +1,31 @@ + + + + + + + + + + \ No newline at end of file diff --git a/samples/devbytes/graphics/ImagePixelization/res/values/dimens.xml b/samples/devbytes/graphics/ImagePixelization/res/values/dimens.xml new file mode 100644 index 000000000..09d93f58d --- /dev/null +++ b/samples/devbytes/graphics/ImagePixelization/res/values/dimens.xml @@ -0,0 +1,20 @@ + + + + 16dp + 16dp + + diff --git a/samples/devbytes/graphics/ImagePixelization/res/values/integers.xml b/samples/devbytes/graphics/ImagePixelization/res/values/integers.xml new file mode 100644 index 000000000..98c1438be --- /dev/null +++ b/samples/devbytes/graphics/ImagePixelization/res/values/integers.xml @@ -0,0 +1,19 @@ + + + + 1000 + + diff --git a/samples/devbytes/graphics/ImagePixelization/res/values/strings.xml b/samples/devbytes/graphics/ImagePixelization/res/values/strings.xml new file mode 100644 index 000000000..35a87c851 --- /dev/null +++ b/samples/devbytes/graphics/ImagePixelization/res/values/strings.xml @@ -0,0 +1,22 @@ + + + + ImagePixelization + Animate + Using AyncTask + Built-in Pixelization + + diff --git a/samples/devbytes/graphics/ImagePixelization/src/com/example/android/imagepixelization/ImagePixelization.java b/samples/devbytes/graphics/ImagePixelization/src/com/example/android/imagepixelization/ImagePixelization.java new file mode 100644 index 000000000..e31d95d5b --- /dev/null +++ b/samples/devbytes/graphics/ImagePixelization/src/com/example/android/imagepixelization/ImagePixelization.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.imagepixelization; + +import android.animation.ObjectAnimator; +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.animation.LinearInterpolator; +import android.widget.ImageView; +import android.widget.SeekBar; + +import java.util.Arrays; + +/** + * This application shows three different graphics/animation concepts. + * + * A pixelization effect is applied to an image with varying pixelization + * factors to achieve an image that is pixelized to varying degrees. In + * order to optimize the amount of image processing performed on the image + * being pixelized, the pixelization effect only takes place if a predefined + * amount of time has elapsed since the main image was last pixelized. The + * effect is also applied when the user stops moving the seekbar. + * + * This application also shows how to use a ValueAnimator to achieve a + * smooth self-animating seekbar. + * + * Lastly, this application shows a use case of AsyncTask where some + * computation heavy processing can be moved onto a background thread, + * so as to keep the UI completely responsive to user input. + */ +public class ImagePixelization extends Activity { + + final private static int SEEKBAR_ANIMATION_DURATION = 10000; + final private static int TIME_BETWEEN_TASKS = 400; + final private static int SEEKBAR_STOP_CHANGE_DELTA = 5; + final private static float PROGRESS_TO_PIXELIZATION_FACTOR = 4000.0f; + + Bitmap mImageBitmap; + ImageView mImageView; + SeekBar mSeekBar; + boolean mIsChecked = false; + boolean mIsBuiltinPixelizationChecked = false; + int mLastProgress = 0; + long mLastTime = 0; + Bitmap mPixelatedBitmap; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_image_pixelization); + + mImageView = (ImageView) findViewById(R.id.pixelView); + mSeekBar = (SeekBar)findViewById(R.id.seekbar); + + mImageBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image); + mImageView.setImageBitmap(mImageBitmap); + + mSeekBar.setOnSeekBarChangeListener(mOnSeekBarChangeListener); + } + + private SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener = + new SeekBar.OnSeekBarChangeListener() { + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + if (Math.abs(mSeekBar.getProgress() - mLastProgress) > SEEKBAR_STOP_CHANGE_DELTA) { + invokePixelization(); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + checkIfShouldPixelize(); + } + }; + + /** + * Checks if enough time has elapsed since the last pixelization call was invoked. + * This prevents too many pixelization processes from being invoked at the same time + * while previous ones have not yet completed. + */ + public void checkIfShouldPixelize() { + if ((System.currentTimeMillis() - mLastTime) > TIME_BETWEEN_TASKS) { + invokePixelization(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.image_pixelization, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected (MenuItem item) { + switch (item.getItemId()){ + case R.id.animate: + ObjectAnimator animator = ObjectAnimator.ofInt(mSeekBar, "progress", 0, + mSeekBar.getMax()); + animator.setInterpolator(new LinearInterpolator()); + animator.setDuration(SEEKBAR_ANIMATION_DURATION); + animator.start(); + break; + case R.id.checkbox: + if (mIsChecked) { + item.setChecked(false); + mIsChecked = false; + } else { + item.setChecked(true); + mIsChecked = true; + } + break; + case R.id.builtin_pixelation_checkbox: + mIsBuiltinPixelizationChecked = !mIsBuiltinPixelizationChecked; + item.setChecked(mIsBuiltinPixelizationChecked); + break; + default: + break; + } + return true; + } + + /** + * A simple pixelization algorithm. This uses a box blur algorithm where all the + * pixels within some region are averaged, and that average pixel value is then + * applied to all the pixels within that region. A higher pixelization factor + * imposes a smaller number of regions of greater size. Similarly, a smaller + * pixelization factor imposes a larger number of regions of smaller size. + */ + public BitmapDrawable customImagePixelization(float pixelizationFactor, Bitmap bitmap) { + + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + + if (mPixelatedBitmap == null || !(width == mPixelatedBitmap.getWidth() && height == + mPixelatedBitmap.getHeight())) { + mPixelatedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + } + + int xPixels = (int) (pixelizationFactor * ((float)width)); + xPixels = xPixels > 0 ? xPixels : 1; + int yPixels = (int) (pixelizationFactor * ((float)height)); + yPixels = yPixels > 0 ? yPixels : 1; + int pixel = 0, red = 0, green = 0, blue = 0, numPixels = 0; + + int[] bitmapPixels = new int[width * height]; + bitmap.getPixels(bitmapPixels, 0, width, 0, 0, width, height); + + int[] pixels = new int[yPixels * xPixels]; + + int maxX, maxY; + + for (int y = 0; y < height; y+=yPixels) { + for (int x = 0; x < width; x+=xPixels) { + + numPixels = red = green = blue = 0; + + maxX = Math.min(x + xPixels, width); + maxY = Math.min(y + yPixels, height); + + for (int i = x; i < maxX; i++) { + for (int j = y; j < maxY; j++) { + pixel = bitmapPixels[j * width + i]; + red += Color.red(pixel); + green += Color.green(pixel); + blue += Color.blue(pixel); + numPixels ++; + } + } + + pixel = Color.rgb(red / numPixels, green / numPixels, blue / numPixels); + + Arrays.fill(pixels, pixel); + + int w = Math.min(xPixels, width - x); + int h = Math.min(yPixels, height - y); + + mPixelatedBitmap.setPixels(pixels, 0 , w, x , y, w, h); + } + } + + return new BitmapDrawable(getResources(), mPixelatedBitmap); + } + + /** + * This method of image pixelization utilizes the bitmap scaling operations built + * into the framework. By downscaling the bitmap and upscaling it back to its + * original size (while setting the filter flag to false), the same effect can be + * achieved with much better performance. + */ + public BitmapDrawable builtInPixelization(float pixelizationFactor, Bitmap bitmap) { + + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + + int downScaleFactorWidth = (int)(pixelizationFactor * width); + downScaleFactorWidth = downScaleFactorWidth > 0 ? downScaleFactorWidth : 1; + int downScaleFactorHeight = (int)(pixelizationFactor * height); + downScaleFactorHeight = downScaleFactorHeight > 0 ? downScaleFactorHeight : 1; + + int downScaledWidth = width / downScaleFactorWidth; + int downScaledHeight = height / downScaleFactorHeight; + + Bitmap pixelatedBitmap = Bitmap.createScaledBitmap(bitmap, downScaledWidth, + downScaledHeight, false); + + /* Bitmap's createScaledBitmap method has a filter parameter that can be set to either + * true or false in order to specify either bilinear filtering or point sampling + * respectively when the bitmap is scaled up or now. + * + * Similarly, a BitmapDrawable also has a flag to specify the same thing. When the + * BitmapDrawable is applied to an ImageView that has some scaleType, the filtering + * flag is taken into consideration. However, for optimization purposes, this flag was + * ignored in BitmapDrawables before Jelly Bean MR1. + * + * Here, it is important to note that prior to JBMR1, two bitmap scaling operations + * are required to achieve the pixelization effect. Otherwise, a BitmapDrawable + * can be created corresponding to the downscaled bitmap such that when it is + * upscaled to fit the ImageView, the upscaling operation is a lot faster since + * it uses internal optimizations to fit the ImageView. + * */ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), pixelatedBitmap); + bitmapDrawable.setFilterBitmap(false); + return bitmapDrawable; + } else { + Bitmap upscaled = Bitmap.createScaledBitmap(pixelatedBitmap, width, height, false); + return new BitmapDrawable(getResources(), upscaled); + } + } + + /** + * Invokes pixelization either on the main thread or on a background thread + * depending on whether or not the checkbox was checked. + */ + public void invokePixelization () { + mLastTime = System.currentTimeMillis(); + mLastProgress = mSeekBar.getProgress(); + if (mIsChecked) { + PixelizeImageAsyncTask asyncPixelateTask = new PixelizeImageAsyncTask(); + asyncPixelateTask.execute(mSeekBar.getProgress() / PROGRESS_TO_PIXELIZATION_FACTOR, + mImageBitmap); + } else { + mImageView.setImageDrawable(pixelizeImage(mSeekBar.getProgress() + / PROGRESS_TO_PIXELIZATION_FACTOR, mImageBitmap)); + } + } + + /** + * Selects either the custom pixelization algorithm that sets and gets bitmap + * pixels manually or the one that uses built-in bitmap operations. + */ + public BitmapDrawable pixelizeImage(float pixelizationFactor, Bitmap bitmap) { + if (mIsBuiltinPixelizationChecked) { + return builtInPixelization(pixelizationFactor, bitmap); + } else { + return customImagePixelization(pixelizationFactor, bitmap); + } + } + + /** + * Implementation of the AsyncTask class showing how to run the + * pixelization algorithm in the background, and retrieving the + * pixelated image from the resulting operation. + */ + private class PixelizeImageAsyncTask extends AsyncTask { + + @Override + protected BitmapDrawable doInBackground(Object... params) { + float pixelizationFactor = (Float)params[0]; + Bitmap originalBitmap = (Bitmap)params[1]; + return pixelizeImage(pixelizationFactor, originalBitmap); + } + + @Override + protected void onPostExecute(BitmapDrawable result) { + mImageView.setImageDrawable(result); + } + + @Override + protected void onPreExecute() { + + } + + @Override + protected void onProgressUpdate(Void... values) { + + } + } +} \ No newline at end of file