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 17 package android.graphics; 18 19 import com.android.ide.common.rendering.api.LayoutLog; 20 import com.android.layoutlib.bridge.Bridge; 21 import com.android.layoutlib.bridge.impl.DelegateManager; 22 import com.android.tools.layoutlib.annotations.LayoutlibDelegate; 23 24 import android.graphics.Shader.TileMode; 25 26 import java.awt.PaintContext; 27 import java.awt.Rectangle; 28 import java.awt.RenderingHints; 29 import java.awt.geom.AffineTransform; 30 import java.awt.geom.NoninvertibleTransformException; 31 import java.awt.geom.Rectangle2D; 32 import java.awt.image.BufferedImage; 33 import java.awt.image.ColorModel; 34 import 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 */ 52 public 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(long nativeMatrix, Bitmap androidBitmap, 79 int shaderTileModeX, 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(nativeMatrix, 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(long matrix, BufferedImage image, 95 TileMode tileModeX, TileMode tileModeY) { 96 super(matrix); 97 mJavaPaint = new BitmapShaderPaint(image, tileModeX, tileModeY); 98 } 99 100 private class BitmapShaderPaint implements java.awt.Paint { 101 private final BufferedImage mImage; 102 private final TileMode mTileModeX; 103 private final TileMode mTileModeY; 104 105 BitmapShaderPaint(BufferedImage image, 106 TileMode tileModeX, TileMode tileModeY) { 107 mImage = image; 108 mTileModeX = tileModeX; 109 mTileModeY = tileModeY; 110 } 111 112 @Override 113 public PaintContext createContext(ColorModel colorModel, Rectangle deviceBounds, 114 Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) { 115 AffineTransform canvasMatrix; 116 try { 117 canvasMatrix = xform.createInverse(); 118 } catch (NoninvertibleTransformException e) { 119 Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, 120 "Unable to inverse matrix in BitmapShader", e, null /*data*/); 121 canvasMatrix = new AffineTransform(); 122 } 123 124 AffineTransform localMatrix = getLocalMatrix(); 125 try { 126 localMatrix = localMatrix.createInverse(); 127 } catch (NoninvertibleTransformException e) { 128 Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_INVERSE, 129 "Unable to inverse matrix in BitmapShader", e, null /*data*/); 130 localMatrix = new AffineTransform(); 131 } 132 133 if (!colorModel.isCompatibleRaster(mImage.getRaster())) { 134 // Fallback to the default ARGB color model 135 colorModel = ColorModel.getRGBdefault(); 136 } 137 138 return new BitmapShaderContext(canvasMatrix, localMatrix, colorModel); 139 } 140 141 private class BitmapShaderContext implements PaintContext { 142 143 private final AffineTransform mCanvasMatrix; 144 private final AffineTransform mLocalMatrix; 145 private final ColorModel mColorModel; 146 147 public BitmapShaderContext( 148 AffineTransform canvasMatrix, 149 AffineTransform localMatrix, 150 ColorModel colorModel) { 151 mCanvasMatrix = canvasMatrix; 152 mLocalMatrix = localMatrix; 153 mColorModel = colorModel; 154 } 155 156 @Override 157 public void dispose() { 158 } 159 160 @Override 161 public ColorModel getColorModel() { 162 return mColorModel; 163 } 164 165 @Override 166 public Raster getRaster(int x, int y, int w, int h) { 167 BufferedImage image = new BufferedImage( 168 mColorModel, mColorModel.createCompatibleWritableRaster(w, h), 169 mColorModel.isAlphaPremultiplied(), null); 170 171 int[] data = new int[w*h]; 172 173 int index = 0; 174 float[] pt1 = new float[2]; 175 float[] pt2 = new float[2]; 176 for (int iy = 0 ; iy < h ; iy++) { 177 for (int ix = 0 ; ix < w ; ix++) { 178 // handle the canvas transform 179 pt1[0] = x + ix; 180 pt1[1] = y + iy; 181 mCanvasMatrix.transform(pt1, 0, pt2, 0, 1); 182 183 // handle the local matrix. 184 pt1[0] = pt2[0]; 185 pt1[1] = pt2[1]; 186 mLocalMatrix.transform(pt1, 0, pt2, 0, 1); 187 188 data[index++] = getColor(pt2[0], pt2[1]); 189 } 190 } 191 192 image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/); 193 194 return image.getRaster(); 195 } 196 } 197 198 /** 199 * Returns a color for an arbitrary point. 200 */ 201 private int getColor(float fx, float fy) { 202 int x = getCoordinate(Math.round(fx), mImage.getWidth(), mTileModeX); 203 int y = getCoordinate(Math.round(fy), mImage.getHeight(), mTileModeY); 204 205 return mImage.getRGB(x, y); 206 } 207 208 private int getCoordinate(int i, int size, TileMode mode) { 209 if (i < 0) { 210 switch (mode) { 211 case CLAMP: 212 i = 0; 213 break; 214 case REPEAT: 215 i = size - 1 - (-i % size); 216 break; 217 case MIRROR: 218 // this is the same as the positive side, just make the value positive 219 // first. 220 i = -i; 221 int count = i / size; 222 i = i % size; 223 224 if ((count % 2) == 1) { 225 i = size - 1 - i; 226 } 227 break; 228 } 229 } else if (i >= size) { 230 switch (mode) { 231 case CLAMP: 232 i = size - 1; 233 break; 234 case REPEAT: 235 i = i % size; 236 break; 237 case MIRROR: 238 int count = i / size; 239 i = i % size; 240 241 if ((count % 2) == 1) { 242 i = size - 1 - i; 243 } 244 break; 245 } 246 } 247 248 return i; 249 } 250 251 252 @Override 253 public int getTransparency() { 254 return java.awt.Paint.TRANSLUCENT; 255 } 256 } 257 } 258