1/* 2 * Copyright (C) 2010 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 17package android.graphics; 18 19import com.android.ide.common.rendering.api.LayoutLog; 20import com.android.layoutlib.bridge.Bridge; 21import com.android.layoutlib.bridge.impl.DelegateManager; 22import com.android.tools.layoutlib.annotations.LayoutlibDelegate; 23 24import android.graphics.Shader.TileMode; 25 26import java.awt.PaintContext; 27import java.awt.Rectangle; 28import java.awt.RenderingHints; 29import java.awt.geom.AffineTransform; 30import java.awt.geom.NoninvertibleTransformException; 31import java.awt.geom.Rectangle2D; 32import java.awt.image.BufferedImage; 33import java.awt.image.ColorModel; 34import java.awt.image.Raster; 35 36/** 37 * Delegate implementing the native methods of android.graphics.BitmapShader 38 * 39 * Through the layoutlib_create tool, the original native methods of BitmapShader have been 40 * replaced by calls to methods of the same name in this delegate class. 41 * 42 * This class behaves like the original native implementation, but in Java, keeping previously 43 * native data into its own objects and mapping them to int that are sent back and forth between 44 * it and the original BitmapShader class. 45 * 46 * Because this extends {@link Shader_Delegate}, there's no need to use a {@link DelegateManager}, 47 * as all the Shader classes will be added to the manager owned by {@link Shader_Delegate}. 48 * 49 * @see Shader_Delegate 50 * 51 */ 52public class BitmapShader_Delegate extends Shader_Delegate { 53 54 // ---- delegate data ---- 55 private java.awt.Paint mJavaPaint; 56 57 // ---- Public Helper methods ---- 58 59 @Override 60 public java.awt.Paint getJavaPaint() { 61 return mJavaPaint; 62 } 63 64 @Override 65 public boolean isSupported() { 66 return true; 67 } 68 69 @Override 70 public String getSupportMessage() { 71 // no message since isSupported returns true; 72 return null; 73 } 74 75 // ---- native methods ---- 76 77 @LayoutlibDelegate 78 /*package*/ static long nativeCreate(Bitmap androidBitmap, int shaderTileModeX, 79 int shaderTileModeY) { 80 Bitmap_Delegate bitmap = Bitmap_Delegate.getDelegate(androidBitmap); 81 if (bitmap == null) { 82 return 0; 83 } 84 85 BitmapShader_Delegate newDelegate = new BitmapShader_Delegate( 86 bitmap.getImage(), 87 Shader_Delegate.getTileMode(shaderTileModeX), 88 Shader_Delegate.getTileMode(shaderTileModeY)); 89 return sManager.addNewDelegate(newDelegate); 90 } 91 92 // ---- Private delegate/helper methods ---- 93 94 private BitmapShader_Delegate(BufferedImage image, 95 TileMode tileModeX, TileMode tileModeY) { 96 mJavaPaint = new BitmapShaderPaint(image, tileModeX, tileModeY); 97 } 98 99 private class BitmapShaderPaint implements java.awt.Paint { 100 private final BufferedImage mImage; 101 private final TileMode mTileModeX; 102 private final TileMode mTileModeY; 103 104 BitmapShaderPaint(BufferedImage image, 105 TileMode tileModeX, TileMode tileModeY) { 106 mImage = image; 107 mTileModeX = tileModeX; 108 mTileModeY = tileModeY; 109 } 110 111 @Override 112 public PaintContext createContext(ColorModel colorModel, Rectangle deviceBounds, 113 Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) { 114 AffineTransform canvasMatrix; 115 try { 116 canvasMatrix = xform.createInverse(); 117 } catch (NoninvertibleTransformException e) { 118 Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, 119 "Unable to inverse matrix in BitmapShader", e, null /*data*/); 120 canvasMatrix = new AffineTransform(); 121 } 122 123 AffineTransform localMatrix = getLocalMatrix(); 124 try { 125 localMatrix = localMatrix.createInverse(); 126 } catch (NoninvertibleTransformException e) { 127 Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, 128 "Unable to inverse matrix in BitmapShader", e, null /*data*/); 129 localMatrix = new AffineTransform(); 130 } 131 132 if (!colorModel.isCompatibleRaster(mImage.getRaster())) { 133 // Fallback to the default ARGB color model 134 colorModel = ColorModel.getRGBdefault(); 135 } 136 137 return new BitmapShaderContext(canvasMatrix, localMatrix, colorModel); 138 } 139 140 private class BitmapShaderContext implements PaintContext { 141 142 private final AffineTransform mCanvasMatrix; 143 private final AffineTransform mLocalMatrix; 144 private final ColorModel mColorModel; 145 146 public BitmapShaderContext( 147 AffineTransform canvasMatrix, 148 AffineTransform localMatrix, 149 ColorModel colorModel) { 150 mCanvasMatrix = canvasMatrix; 151 mLocalMatrix = localMatrix; 152 mColorModel = colorModel; 153 } 154 155 @Override 156 public void dispose() { 157 } 158 159 @Override 160 public ColorModel getColorModel() { 161 return mColorModel; 162 } 163 164 @Override 165 public Raster getRaster(int x, int y, int w, int h) { 166 BufferedImage image = new BufferedImage( 167 mColorModel, mColorModel.createCompatibleWritableRaster(w, h), 168 mColorModel.isAlphaPremultiplied(), null); 169 170 int[] data = new int[w*h]; 171 172 int index = 0; 173 float[] pt1 = new float[2]; 174 float[] pt2 = new float[2]; 175 for (int iy = 0 ; iy < h ; iy++) { 176 for (int ix = 0 ; ix < w ; ix++) { 177 // handle the canvas transform 178 pt1[0] = x + ix; 179 pt1[1] = y + iy; 180 mCanvasMatrix.transform(pt1, 0, pt2, 0, 1); 181 182 // handle the local matrix. 183 pt1[0] = pt2[0]; 184 pt1[1] = pt2[1]; 185 mLocalMatrix.transform(pt1, 0, pt2, 0, 1); 186 187 data[index++] = getColor(pt2[0], pt2[1]); 188 } 189 } 190 191 image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/); 192 193 return image.getRaster(); 194 } 195 } 196 197 /** 198 * Returns a color for an arbitrary point. 199 */ 200 private int getColor(float fx, float fy) { 201 int x = getCoordinate(Math.round(fx), mImage.getWidth(), mTileModeX); 202 int y = getCoordinate(Math.round(fy), mImage.getHeight(), mTileModeY); 203 204 return mImage.getRGB(x, y); 205 } 206 207 private int getCoordinate(int i, int size, TileMode mode) { 208 if (i < 0) { 209 switch (mode) { 210 case CLAMP: 211 i = 0; 212 break; 213 case REPEAT: 214 i = size - 1 - (-i % size); 215 break; 216 case MIRROR: 217 // this is the same as the positive side, just make the value positive 218 // first. 219 i = -i; 220 int count = i / size; 221 i = i % size; 222 223 if ((count % 2) == 1) { 224 i = size - 1 - i; 225 } 226 break; 227 } 228 } else if (i >= size) { 229 switch (mode) { 230 case CLAMP: 231 i = size - 1; 232 break; 233 case REPEAT: 234 i = i % size; 235 break; 236 case MIRROR: 237 int count = i / size; 238 i = i % size; 239 240 if ((count % 2) == 1) { 241 i = size - 1 - i; 242 } 243 break; 244 } 245 } 246 247 return i; 248 } 249 250 251 @Override 252 public int getTransparency() { 253 return java.awt.Paint.TRANSLUCENT; 254 } 255 } 256} 257