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(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