1 /* 2 * Copyright (C) 2009 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 17 package com.cooliris.media; 18 19 import android.graphics.Bitmap; 20 import android.graphics.Canvas; 21 import android.graphics.LightingColorFilter; 22 import android.graphics.Paint; 23 24 // CR: class comment 25 public final class AdaptiveBackgroundTexture extends Texture { 26 private static final int RED_MASK = 0xff0000; 27 private static final int RED_MASK_SHIFT = 16; 28 private static final int GREEN_MASK = 0x00ff00; 29 private static final int GREEN_MASK_SHIFT = 8; 30 private static final int BLUE_MASK = 0x0000ff; 31 private static final int RADIUS = 4; 32 private static final int KERNEL_SIZE = RADIUS * 2 + 1; 33 private static final int NUM_COLORS = 256; 34 private static final int MAX_COLOR_VALUE = NUM_COLORS - 1; 35 private static final int[] KERNEL_NORM = new int[KERNEL_SIZE * NUM_COLORS]; 36 private static final int MULTIPLY_COLOR = 0xffaaaaaa; 37 private static final int START_FADE_X = 96; 38 private static final int THUMBNAIL_MAX_X = 128; 39 40 private final int mWidth; 41 private final int mHeight; 42 private final Bitmap mSource; 43 private Texture mBaseTexture; 44 45 static { 46 // Build a lookup table from summed to normalized kernel values. 47 for (int i = KERNEL_SIZE * NUM_COLORS - 1; i >= 0; --i) { 48 KERNEL_NORM[i] = i / KERNEL_SIZE; 49 } 50 } 51 52 public AdaptiveBackgroundTexture(Bitmap source, int width, int height) { 53 mSource = source; 54 mWidth = width; 55 mHeight = height; 56 mBaseTexture = null; 57 } 58 59 public AdaptiveBackgroundTexture(Texture texture, int width, int height) { 60 mBaseTexture = texture; 61 mSource = null; 62 mWidth = width; 63 mHeight = height; 64 } 65 66 @Override 67 protected boolean shouldQueue() { 68 return true; 69 } 70 71 @Override 72 public boolean isCached() { 73 return true; 74 } 75 76 @Override 77 protected Bitmap load(RenderView view) { 78 // Determine a crop rectangle for the source image that is the aspect 79 // ratio of the destination. 80 Bitmap source = mSource; 81 if (source == null) { 82 if (mBaseTexture != null) { 83 source = mBaseTexture.load(view); 84 if (source == null) { 85 return null; 86 } 87 } else { 88 return null; 89 } 90 } 91 source = Utils.resizeBitmap(source, THUMBNAIL_MAX_X); 92 int sourceWidth = source.getWidth(); 93 int sourceHeight = source.getHeight(); 94 int destWidth = mWidth; 95 int destHeight = mHeight; 96 float fitX = (float) sourceWidth / (float) destWidth; 97 float fitY = (float) sourceHeight / (float) destHeight; 98 float scale; 99 int cropX; 100 int cropY; 101 int cropWidth; 102 int cropHeight; 103 if (fitX < fitY) { 104 // Full width, partial height. 105 cropWidth = sourceWidth; 106 cropHeight = (int) (destHeight * fitX); 107 cropX = 0; 108 cropY = (sourceHeight - cropHeight) / 2; 109 scale = 1.0f / fitX; 110 } else { 111 // Full height, partial or full width. 112 cropWidth = (int) (destHeight * fitY); 113 cropHeight = sourceHeight; 114 cropX = (sourceWidth - cropWidth) / 2; 115 cropY = 0; 116 scale = 1f / fitY; 117 } 118 119 // Create a source and destination buffer for the image. 120 int numPixels = cropWidth * cropHeight; 121 int[] in = new int[numPixels]; 122 int[] tmp = new int[numPixels]; 123 124 // Get the source pixels as 32-bit ARGB. 125 source.getPixels(in, 0, cropWidth, cropX, cropY, cropWidth, cropHeight); 126 127 // Box blur is a separable kernel, so it is decomposed into a horizontal 128 // and vertical pass. 129 // The filter function applies the kernel across each row and transposes 130 // the output. 131 // Hence we apply it twice to provide efficient horizontal and vertical 132 // convolution. 133 // The filter discards the alpha channel. 134 boxBlurFilter(in, tmp, cropWidth, cropHeight, cropWidth); 135 boxBlurFilter(tmp, in, cropHeight, cropWidth, START_FADE_X); 136 137 // Return a bitmap scaled to the desired size. 138 Bitmap filtered = Bitmap.createBitmap(in, cropWidth, cropHeight, Bitmap.Config.ARGB_8888); 139 140 // Composite the bitmap scaled to the target size and darken the pixels. 141 Bitmap output = Bitmap.createBitmap(destWidth, destHeight, Bitmap.Config.ARGB_8888); 142 Canvas canvas = new Canvas(output); 143 Paint paint = new Paint(); 144 paint.setFilterBitmap(true); 145 paint.setDither(true); 146 paint.setColorFilter(new LightingColorFilter(MULTIPLY_COLOR, 0)); 147 canvas.scale(scale, scale); 148 canvas.drawBitmap(filtered, 0f, 0f, paint); 149 filtered.recycle(); 150 151 // Clear the texture 152 mBaseTexture = null; 153 return output; 154 } 155 156 private static void boxBlurFilter(int[] in, int[] out, int width, int height, int startFadeX) { 157 int inPos = 0; 158 int maxX = width - 1; 159 for (int y = 0; y < height; ++y) { 160 // Evaluate the kernel for the first pixel in the row. 161 int red = 0; 162 int green = 0; 163 int blue = 0; 164 for (int i = -RADIUS; i <= RADIUS; ++i) { 165 int argb = in[inPos + FloatUtils.clamp(i, 0, maxX)]; 166 red += (argb & RED_MASK) >> RED_MASK_SHIFT; 167 green += (argb & GREEN_MASK) >> GREEN_MASK_SHIFT; 168 blue += argb & BLUE_MASK; 169 } 170 // Compute the alpha value. 171 int alpha = (y < startFadeX) ? 0xff : ((height - y - 1) * MAX_COLOR_VALUE / (height - startFadeX)); 172 // Compute output values for the row. 173 int outPos = y; 174 for (int x = 0; x != width; ++x) { // CR: x < width 175 // Output the current pixel. 176 out[outPos] = (alpha << 24) | (KERNEL_NORM[red] << RED_MASK_SHIFT) | (KERNEL_NORM[green] << GREEN_MASK_SHIFT) 177 | KERNEL_NORM[blue]; 178 // Slide to the next pixel, adding the new rightmost pixel and 179 // subtracting the former leftmost. 180 int prevX = FloatUtils.clamp(x - RADIUS, 0, maxX); 181 int nextX = FloatUtils.clamp(x + RADIUS + 1, 0, maxX); 182 int prevArgb = in[inPos + prevX]; 183 int nextArgb = in[inPos + nextX]; 184 red += ((nextArgb & RED_MASK) - (prevArgb & RED_MASK)) >> RED_MASK_SHIFT; 185 green += ((nextArgb & GREEN_MASK) - (prevArgb & GREEN_MASK)) >> GREEN_MASK_SHIFT; 186 blue += (nextArgb & BLUE_MASK) - (prevArgb & BLUE_MASK); 187 outPos += height; 188 } 189 inPos += width; 190 } 191 } 192 } 193