| /* |
| * Copyright (C) 2009 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.cooliris.media; |
| |
| import android.graphics.Bitmap; |
| import android.graphics.Canvas; |
| import android.graphics.LightingColorFilter; |
| import android.graphics.Paint; |
| |
| // CR: class comment |
| public final class AdaptiveBackgroundTexture extends Texture { |
| private static final int RED_MASK = 0xff0000; |
| private static final int RED_MASK_SHIFT = 16; |
| private static final int GREEN_MASK = 0x00ff00; |
| private static final int GREEN_MASK_SHIFT = 8; |
| private static final int BLUE_MASK = 0x0000ff; |
| private static final int RADIUS = 4; |
| private static final int KERNEL_SIZE = RADIUS * 2 + 1; |
| private static final int NUM_COLORS = 256; |
| private static final int MAX_COLOR_VALUE = NUM_COLORS - 1; |
| private static final int[] KERNEL_NORM = new int[KERNEL_SIZE * NUM_COLORS]; |
| private static final int MULTIPLY_COLOR = 0xffaaaaaa; |
| private static final int START_FADE_X = 96; |
| private static final int THUMBNAIL_MAX_X = 128; |
| |
| private final int mWidth; |
| private final int mHeight; |
| private final Bitmap mSource; |
| private Texture mBaseTexture; |
| |
| static { |
| // Build a lookup table from summed to normalized kernel values. |
| for (int i = KERNEL_SIZE * NUM_COLORS - 1; i >= 0; --i) { |
| KERNEL_NORM[i] = i / KERNEL_SIZE; |
| } |
| } |
| |
| public AdaptiveBackgroundTexture(Bitmap source, int width, int height) { |
| mSource = source; |
| mWidth = width; |
| mHeight = height; |
| mBaseTexture = null; |
| } |
| |
| public AdaptiveBackgroundTexture(Texture texture, int width, int height) { |
| mBaseTexture = texture; |
| mSource = null; |
| mWidth = width; |
| mHeight = height; |
| } |
| |
| @Override |
| protected boolean shouldQueue() { |
| return true; |
| } |
| |
| @Override |
| public boolean isCached() { |
| return true; |
| } |
| |
| @Override |
| protected Bitmap load(RenderView view) { |
| // Determine a crop rectangle for the source image that is the aspect |
| // ratio of the destination. |
| Bitmap source = mSource; |
| if (source == null) { |
| if (mBaseTexture != null) { |
| source = mBaseTexture.load(view); |
| if (source == null) { |
| return null; |
| } |
| } else { |
| return null; |
| } |
| } |
| source = Utils.resizeBitmap(source, THUMBNAIL_MAX_X); |
| int sourceWidth = source.getWidth(); |
| int sourceHeight = source.getHeight(); |
| int destWidth = mWidth; |
| int destHeight = mHeight; |
| float fitX = (float) sourceWidth / (float) destWidth; |
| float fitY = (float) sourceHeight / (float) destHeight; |
| float scale; |
| int cropX; |
| int cropY; |
| int cropWidth; |
| int cropHeight; |
| if (fitX < fitY) { |
| // Full width, partial height. |
| cropWidth = sourceWidth; |
| cropHeight = (int) (destHeight * fitX); |
| cropX = 0; |
| cropY = (sourceHeight - cropHeight) / 2; |
| scale = 1.0f / fitX; |
| } else { |
| // Full height, partial or full width. |
| cropWidth = (int) (destHeight * fitY); |
| cropHeight = sourceHeight; |
| cropX = (sourceWidth - cropWidth) / 2; |
| cropY = 0; |
| scale = 1f / fitY; |
| } |
| |
| // Create a source and destination buffer for the image. |
| int numPixels = cropWidth * cropHeight; |
| int[] in = new int[numPixels]; |
| int[] tmp = new int[numPixels]; |
| |
| // Get the source pixels as 32-bit ARGB. |
| source.getPixels(in, 0, cropWidth, cropX, cropY, cropWidth, cropHeight); |
| |
| // Box blur is a separable kernel, so it is decomposed into a horizontal |
| // and vertical pass. |
| // The filter function applies the kernel across each row and transposes |
| // the output. |
| // Hence we apply it twice to provide efficient horizontal and vertical |
| // convolution. |
| // The filter discards the alpha channel. |
| boxBlurFilter(in, tmp, cropWidth, cropHeight, cropWidth); |
| boxBlurFilter(tmp, in, cropHeight, cropWidth, START_FADE_X); |
| |
| // Return a bitmap scaled to the desired size. |
| Bitmap filtered = Bitmap.createBitmap(in, cropWidth, cropHeight, Bitmap.Config.ARGB_8888); |
| |
| // Composite the bitmap scaled to the target size and darken the pixels. |
| Bitmap output = Bitmap.createBitmap(destWidth, destHeight, Bitmap.Config.ARGB_8888); |
| Canvas canvas = new Canvas(output); |
| Paint paint = new Paint(); |
| paint.setFilterBitmap(true); |
| paint.setDither(true); |
| paint.setColorFilter(new LightingColorFilter(MULTIPLY_COLOR, 0)); |
| canvas.scale(scale, scale); |
| canvas.drawBitmap(filtered, 0f, 0f, paint); |
| filtered.recycle(); |
| |
| // Clear the texture |
| mBaseTexture = null; |
| return output; |
| } |
| |
| private static void boxBlurFilter(int[] in, int[] out, int width, int height, int startFadeX) { |
| int inPos = 0; |
| int maxX = width - 1; |
| for (int y = 0; y < height; ++y) { |
| // Evaluate the kernel for the first pixel in the row. |
| int red = 0; |
| int green = 0; |
| int blue = 0; |
| for (int i = -RADIUS; i <= RADIUS; ++i) { |
| int argb = in[inPos + FloatUtils.clamp(i, 0, maxX)]; |
| red += (argb & RED_MASK) >> RED_MASK_SHIFT; |
| green += (argb & GREEN_MASK) >> GREEN_MASK_SHIFT; |
| blue += argb & BLUE_MASK; |
| } |
| // Compute the alpha value. |
| int alpha = (y < startFadeX) ? 0xff : ((height - y - 1) * MAX_COLOR_VALUE / (height - startFadeX)); |
| // Compute output values for the row. |
| int outPos = y; |
| for (int x = 0; x != width; ++x) { // CR: x < width |
| // Output the current pixel. |
| out[outPos] = (alpha << 24) | (KERNEL_NORM[red] << RED_MASK_SHIFT) | (KERNEL_NORM[green] << GREEN_MASK_SHIFT) |
| | KERNEL_NORM[blue]; |
| // Slide to the next pixel, adding the new rightmost pixel and |
| // subtracting the former leftmost. |
| int prevX = FloatUtils.clamp(x - RADIUS, 0, maxX); |
| int nextX = FloatUtils.clamp(x + RADIUS + 1, 0, maxX); |
| int prevArgb = in[inPos + prevX]; |
| int nextArgb = in[inPos + nextX]; |
| red += ((nextArgb & RED_MASK) - (prevArgb & RED_MASK)) >> RED_MASK_SHIFT; |
| green += ((nextArgb & GREEN_MASK) - (prevArgb & GREEN_MASK)) >> GREEN_MASK_SHIFT; |
| blue += (nextArgb & BLUE_MASK) - (prevArgb & BLUE_MASK); |
| outPos += height; |
| } |
| inPos += width; |
| } |
| } |
| } |