blob: 980a6736fa8b6fb6db493065fa739b21c87a7599 [file] [log] [blame]
/*
* 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;
}
}
}