/* * Copyright (C) 2006 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 android.graphics; import java.util.ArrayList; import java.util.HashMap; import com.intel.mpt.annotation.MayloonStubAnnotation; import android.graphics.Bitmap.Config; import android.graphics.Paint.Align; import android.graphics.Paint.Cap; import android.graphics.Paint.Join; import android.graphics.Paint.ShaderType; import android.graphics.Path.Direction; import android.graphics.PorterDuff.Mode; import android.util.Log; /** * The Canvas class holds the "draw" calls. To draw something, you need * 4 basic components: A Bitmap to hold the pixels, a Canvas to host * the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, * Path, text, Bitmap), and a paint (to describe the colors and styles for the * drawing). * MayLoon supports Canvas by using HTML5 Canvas object by setting its CSS position property * */ public class Canvas { private static final String TAG = "Canvas"; // Package-scoped for quick access. /*package*/ int mDensity = Bitmap.DENSITY_NONE; private int saveCount = 0; public float alpha = -1; static final boolean DEBUG = false; private ArrayList<CanvasState> transList = new ArrayList<CanvasState>(); int _ddx; int _ddy; int _width; int _height; Matrix ctm = null; // current transformation matrix Rect mClipBounds = new Rect(); // current clipBounds private class CanvasState { public Rect _rect = null; public Rect _clipBounds = null; public Matrix _ctm = null; // save ctm } private Bitmap mBM; private final String appCanvasID; private final String surfaceViewCanvasID; public String getCanvasID() { return appCanvasID; } public String getSurfaceViewCanvasID() { return surfaceViewCanvasID; } public static int APP_CANVAS = 0; public static int SURFACEVIEW_CANVAS = 2; private int canvasType = 0; private String activeCanvas = null; private boolean opaqueFlag = false; // the SAVE_FLAG constants must match their native equivalents /** restore the current matrix when restore() is called */ public static final int MATRIX_SAVE_FLAG = 0x01; /** restore the current clip when restore() is called */ public static final int CLIP_SAVE_FLAG = 0x02; /** the layer needs to per-pixel alpha */ public static final int HAS_ALPHA_LAYER_SAVE_FLAG = 0x04; /** the layer needs to 8-bits per color component */ public static final int FULL_COLOR_LAYER_SAVE_FLAG = 0x08; /** clip against the layer's bounds */ public static final int CLIP_TO_LAYER_SAVE_FLAG = 0x10; /** restore everything when restore() is called */ public static final int ALL_SAVE_FLAG = 0x1F; // Used to map Android Paint styles to HTML5 styles private static HashMap<Object, String> paintCapAndJoinMap = new HashMap(); private void initPaintCapAndJoinMap() { paintCapAndJoinMap.put(Cap.BUTT, "butt"); paintCapAndJoinMap.put(Cap.ROUND, "round"); paintCapAndJoinMap.put(Cap.SQUARE, "square"); paintCapAndJoinMap.put(Join.MITER, "miter"); paintCapAndJoinMap.put(Join.ROUND, "round"); paintCapAndJoinMap.put(Join.BEVEL, "bevel"); paintCapAndJoinMap.put(Align.LEFT, "left"); paintCapAndJoinMap.put(Align.CENTER, "center"); paintCapAndJoinMap.put(Align.RIGHT, "right"); } private void initSave() { saveCount++; CanvasState state = new CanvasState(); state._rect = new Rect(_ddx, _ddy, _ddx + _width, _ddy + _height); state._clipBounds = new Rect(mClipBounds); state._ctm = new Matrix(this.ctm); transList.add(state); } public enum EdgeType { BW(0), //!< treat edges by just rounding to nearest pixel boundary AA(1); //!< treat edges by rounding-out, since they may be antialiased EdgeType(int nativeInt) { this.nativeInt = nativeInt; } final int nativeInt; } public enum VertexMode { TRIANGLES(0), TRIANGLE_STRIP(1), TRIANGLE_FAN(2); VertexMode(int nativeInt) { this.nativeInt = nativeInt; } final int nativeInt; } public void chooseCanvas(int _canvasType) { canvasType = _canvasType; if (canvasType == APP_CANVAS) { activeCanvas = appCanvasID; } else if (canvasType == SURFACEVIEW_CANVAS) { activeCanvas = surfaceViewCanvasID; } } public String getHTML5CanvasID() { return activeCanvas; } /** * Construct a canvas with the specified bitmap to draw into. The bitmap * must be mutable. * <p> * The initial target density of the canvas is the same as the given * bitmap's density. * * @param bitmap Specifies a mutable bitmap for the canvas to draw into. */ public Canvas(Bitmap bitmap) { if (!bitmap.isMutable()) { throw new IllegalStateException( "Immutable bitmap passed to Canvas constructor"); } throwIfRecycled(bitmap); this.mBM = bitmap; this.appCanvasID = null; this.surfaceViewCanvasID = null; this.ctm = new Matrix(); // initialize the matrix with identity // Ensure Cached Canvas is created for this bitmap this.mBM.ensureCachedCanvas(false, false); initPaintCapAndJoinMap(); mClipBounds.set(_ddx, _ddy, _ddx + bitmap.getWidth(), _ddy + bitmap.getHeight()); initSave(); } /** * create a canvas element named id, and then attach it to the document * * @param id */ public Canvas(String canvasID) { this.appCanvasID = canvasID; this.surfaceViewCanvasID = null; this.ctm = new Matrix(); this.chooseCanvas(Canvas.APP_CANVAS); initPaintCapAndJoinMap(); initSave(); } public Canvas(String canvasID, String surfaceViewCanvasID) { this.appCanvasID = canvasID; this.surfaceViewCanvasID = surfaceViewCanvasID; this.ctm = new Matrix(); initPaintCapAndJoinMap(); initSave(); } public Canvas() { this.appCanvasID = null; this.surfaceViewCanvasID = null; this.ctm = new Matrix(); clipRect(0, 0, getWidth(), getHeight()); initPaintCapAndJoinMap(); initSave(); } public void setOpaque(boolean opaque) { this.opaqueFlag = opaque; } public boolean isOpaque() { return this.opaqueFlag; } /** * set HTML5 Canvas Context properties according the paint * * @param paint */ private void setHTML5CanvasContext(Paint paint) { // line style String rgb = Color.toString(paint.getColor()); float strokeWidth = paint.getStrokeWidth(); String strokeCap = paintCapAndJoinMap.get(paint.getStrokeCap()); String strokeJoin = paintCapAndJoinMap.get(paint.getStrokeJoin()); float strokeMiter = paint.getStrokeMiter(); // font style Typeface typeface = paint.getTypeface(); String textAlign = paintCapAndJoinMap.get(paint.getTextAlign()); String font = null; if (typeface != null) { // convert Typeface to HTML5 font string font = typeface.getStyleName(); font += " "; font += paint.getTextSize() + "px"; font += " "; font += typeface.getFamilyName(); } else { // Set the font property into paint's canvas font = paint.setFontCanvasProperties(); } // get Paint's shader Shader shader = paint.getShader(); ShaderType shaderType = paint.getShaderType(); // get Paint's xfermode Xfermode xfermode = paint.getXfermode(); if (xfermode != null && !(xfermode instanceof PorterDuffXfermode)) { // We only support PorterDuffXfermode now. xfermode = null; } /** * @j2sNative * var canvas = null; * if (this.mBM != null) { * canvas = this.mBM.mCachedCanvas; * } else { * canvas = document.getElementById(this.activeCanvas); * } * var context = canvas.getContext("2d"); * * // line style setting * context.fillStyle = rgb; * context.strokeStyle = rgb; * context.lineWidth = strokeWidth; * context.lineCap = strokeCap; * context.lineJoin = strokeJoin; * context.miterLimit = strokeMiter; * // font setting * if (font != null) { * context.font = font; * } * context.textAlign = textAlign; * * // set Shader if we have. * var gradient = null; * if (shader != null) { * if (shaderType == shaderType.BITMAPSHADER) { * // We should detect whether browser supports Pattern now. * if (context.createPattern == null) { * android.util.Log.e(this.TAG, "This browser doesn't support createPattern"); * return; * } * if (!shader.mBitmap.ensureCachedCanvas(false, true)) return; * if (shader.mTileMode == null) return; // only repeat mode is supported. * var bitmapshader = context.createPattern(shader.mBitmap.mCachedCanvas, shader.mTileMode); * context.fillStyle = bitmapshader; * context.strokeStyle = bitmapshader; * } * if (shaderType == shaderType.COMPOSESHADER) { * android.util.Log.e(this.TAG, "ComposeShader is not implemented!"); * } * if (shaderType == shaderType.LINEARGRADIENT) { * gradient = context.createLinearGradient(shader.m_pts0.x, shader.m_pts0.y, * shader.m_pts1.x, shader.m_pts1.y); * } * if (shaderType == shaderType.RADIALGRADIENT) { * gradient = context.createRadialGradient(shader.m_center.x, shader.m_center.y, 0, * shader.m_center.x, shader.m_center.y, shader.m_radius); * } * if (shaderType == shaderType.SWEEPGRADIENT) { * android.util.Log.e(this.TAG, "SweepGradient is not implemented!"); * } * var alpha = 0; * if (gradient != null) { * if (shader.m_positions != null) { * for (var i = 0; i < shader.m_positions.length; i++) { * gradient.addColorStop(shader.m_positions[i], android.graphics.Color.toString(shader.m_colors[i])); * alpha += android.graphics.Color.alpha(shader.m_colors[i]) / 255; * } * * } else { * // Add color stop evenly. * for (var i = 0; i < shader.m_colors.length; i++) { * gradient.addColorStop(i/(shader.m_colors.length-1), android.graphics.Color.toString(shader.m_colors[i])); * alpha += android.graphics.Color.alpha(shader.m_colors[i]) / 255; * } * } * * if (shader.m_positions != null && shader.m_positions.length > 0) { * alpha = alpha / shader.m_positions.length; * } else if (shader.m_colors != null && shader.m_colors.length > 0) { * alpha = alpha / shader.m_colors.length; * } else { * alpha = 1; * } * * context.globalAlpha = alpha; * context.fillStyle = gradient; * context.strokeStyle = gradient; * } * } * * if (this.alpha != -1) { * context.globalAlpha = this.alpha; * } * * if (xfermode != null) { * context.globalCompositeOperation = "source-over"; // default * switch (xfermode.native_instance) { * case android.graphics.PorterDuff.Mode.CLEAR.nativeInt: * android.util.Log.e(this.TAG, "PorterDuff.Mode.CLEAR is not supported!"); * break; * case android.graphics.PorterDuff.Mode.DARKEN.nativeInt: * context.globalCompositeOperation = "darker"; * break; * case android.graphics.PorterDuff.Mode.DST.nativeInt: * android.util.Log.e(this.TAG, "PorterDuff.Mode.DST is not supported!"); * break; * case android.graphics.PorterDuff.Mode.DST_ATOP.nativeInt: * context.globalCompositeOperation = "destination-atop"; * break; * case android.graphics.PorterDuff.Mode.DST_IN.nativeInt: * context.globalCompositeOperation = "destination-in"; * break; * case android.graphics.PorterDuff.Mode.DST_OUT.nativeInt: * context.globalCompositeOperation = "destination-out"; * break; * case android.graphics.PorterDuff.Mode.DST_OVER.nativeInt: * context.globalCompositeOperation = "destination-over"; * break; * case android.graphics.PorterDuff.Mode.LIGHTEN.nativeInt: * context.globalCompositeOperation = "lighter"; * break; * case android.graphics.PorterDuff.Mode.MULTIPLY.nativeInt: * android.util.Log.e(this.TAG, "PorterDuff.Mode.MULTIPLY is not supported!"); * break; * case android.graphics.PorterDuff.Mode.SCREEN.nativeInt: * android.util.Log.e(this.TAG, "PorterDuff.Mode.SCREEN is not supported!"); * break; * case android.graphics.PorterDuff.Mode.SRC.nativeInt: * context.globalCompositeOperation = "copy"; * break; * case android.graphics.PorterDuff.Mode.SRC_ATOP.nativeInt: * context.globalCompositeOperation = "source-atop"; * break; * case android.graphics.PorterDuff.Mode.SRC_IN.nativeInt: * context.globalCompositeOperation = "source-in"; * break; * case android.graphics.PorterDuff.Mode.SRC_OUT.nativeInt: * context.globalCompositeOperation = "source-out"; * break; * case android.graphics.PorterDuff.Mode.SRC_OVER.nativeInt: * context.globalCompositeOperation = "source-over"; // default * break; * case android.graphics.PorterDuff.Mode.XOR.nativeInt: * context.globalCompositeOperation = "xor"; * break; * default: * break; * } * } */{} } /** * Fill the entire canvas' bitmap (restricted to the current clip) with the * specified RGB color, using srcover porterduff mode. * * @param r red component (0..255) of the color to draw onto the canvas * @param g green component (0..255) of the color to draw onto the canvas * @param b blue component (0..255) of the color to draw onto the canvas */ public void drawRGB(int r, int g, int b) { this.drawARGB(0xFF, r, g, b); } /** * Fill the entire canvas' bitmap (restricted to the current clip) with the * specified ARGB color, using srcover porterduff mode. * * @param a alpha component (0..255) of the color to draw onto the canvas * @param r red component (0..255) of the color to draw onto the canvas * @param g green component (0..255) of the color to draw onto the canvas * @param b blue component (0..255) of the color to draw onto the canvas */ public void drawARGB(int a, int r, int g, int b) { Paint paint = new Paint(); paint.setARGB(a, r, g, b); setHTML5CanvasContext(paint); /** * @j2sNative * var _canvas = null; * if (this.mBM != null) { * _canvas = this.mBM.mCachedCanvas; * // Update the bitmap cached canvas dirty flag * this.mBM.mIsCachedCanvasDirty = true; * } else { * _canvas = document.getElementById(this.activeCanvas); * } * var _context = _canvas.getContext("2d"); * _context.fillRect(0, 0, _canvas.width, _canvas.height); */ {} } /** * Draw the text, with origin at (x,y), using the specified paint. The * origin is interpreted based on the Align setting in the paint. * * @param text The text to be drawn * @param x The x-coordinate of the origin of the text being drawn * @param y The y-coordinate of the origin of the text being drawn * @param paint The paint used for the text (e.g. color, size, style) */ public void drawText(char[] text, int index, int count, float x, float y, Paint paint) { if ((index | count | (index + count) | (text.length - index - count)) < 0) { throw new IndexOutOfBoundsException(); } this.drawText(new String(text), index, index + count, x, y, paint); } /** * Draw the text, with origin at (x,y), using the specified paint. * The origin is interpreted based on the Align setting in the paint. * * @param text The text to be drawn * @param start The index of the first character in text to draw * @param end (end - 1) is the index of the last character in text to draw * @param x The x-coordinate of the origin of the text being drawn * @param y The y-coordinate of the origin of the text being drawn * @param paint The paint used for the text (e.g. color, size, style) */ public void drawText(String text, int start, int end, float x, float y, Paint paint) { if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } String subText = text.substring(start, end); if (paint != null) { Xfermode xfermode = paint.getXfermode(); if (xfermode != null && xfermode instanceof PorterDuffXfermode) { if (xfermode.native_instance == PorterDuff.Mode.DST.nativeInt) { // for dst mode, as HTML5 doesn't support DST mode natively, // we just skip the drawing in MayLoon return; } } setHTML5CanvasContext(paint); } /** * @j2sNative * var _canvas = null; * if (this.mBM != null) { * _canvas = this.mBM.mCachedCanvas; * // Update the bitmap cached canvas dirty flag * this.mBM.mIsCachedCanvasDirty = true; * } else { * _canvas = document.getElementById(this.activeCanvas); * } * var _context = _canvas.getContext("2d"); * _context.fillText(subText, x, y); */{} } /** * Draw the specified range of text, specified by start/end, with its * origin at (x,y), in the specified Paint. The origin is interpreted * based on the Align setting in the Paint. * * @param text The text to be drawn * @param start The index of the first character in text to draw * @param end (end - 1) is the index of the last character in text * to draw * @param x The x-coordinate of origin for where to draw the text * @param y The y-coordinate of origin for where to draw the text * @param paint The paint used for the text (e.g. color, size, style) */ public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) { this.drawText(text.toString(), start, end, x, y, paint); } /** * Draw the text, with origin at (x,y), using the specified paint. The * origin is interpreted based on the Align setting in the paint. * * @param text The text to be drawn * @param x The x-coordinate of the origin of the text being drawn * @param y The y-coordinate of the origin of the text being drawn * @param paint The paint used for the text (e.g. color, size, style) */ public void drawText(String text, float x, float y, Paint paint) { this.drawText(text, 0, text.length(), x, y, paint); } /** * Draw the text in the array, with each character's origin specified by * the pos array. * * @param text The text to be drawn * @param index The index of the first character to draw * @param count The number of characters to draw, starting from index. * @param pos Array of [x,y] positions, used to position each * character * @param paint The paint used for the text (e.g. color, size, style) */ public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) { if (index < 0 || index + count > text.length || count*2 > pos.length) { throw new IndexOutOfBoundsException(); } for(int i = index; i < index + count; i++) { this.drawText(String.valueOf(text[i]), pos[2 * i], pos[2 * i + 1], paint); } } /** * Draw the text in the array, with each character's origin specified by * the pos array. * * @param text The text to be drawn * @param pos Array of [x,y] positions, used to position each character * @param paint The paint used for the text (e.g. color, size, style) */ public void drawPosText(String text, float[] pos, Paint paint) { if (text.length()*2 > pos.length) { throw new ArrayIndexOutOfBoundsException(); } int len = text.length(); for(int i = 0; i < len; i++) { this.drawText(text.substring(i, i + 1), pos[2 * i], pos[2 * i + 1], paint); } } /** * Fill the entire canvas' bitmap (restricted to the current clip) with the * specified color, using srcover porterduff mode. * * @param color the color to draw onto the canvas */ public void drawColor(int color) { this.drawARGB(Color.alpha(color), Color.red(color), Color.green(color), Color.blue(color)); } /** * Fill the entire canvas' bitmap (restricted to the current clip) with the * specified color and porter-duff xfermode. * * @param color the color to draw with * @param mode the porter-duff mode to apply to the color */ public void drawColor(int color, PorterDuff.Mode mode) { if (mode == Mode.CLEAR) { this.drawColor(color); } else { // TODO: Implement it using mode Log.e(TAG, "Only Mode.CLEAR is implemented now!"); } } public void drawGradient(int startColor, int endColor, int angle) { String start = Color.toString(startColor); String end = Color.toString(endColor); /** * @j2sNative * var _canvas = null; * if (this.mBM != null) { * _canvas = this.mBM.mCachedCanvas; * // Update the bitmap cached canvas dirty flag * this.mBM.mIsCachedCanvasDirty = true; * } else { * _canvas = document.getElementById(this.activeCanvas); * } * var _context = _canvas.getContext("2d"); * var grad = _context.createLinearGradient(0, 0, 0, this._height); * grad.addColorStop(0, start); * grad.addColorStop(1, end); * _context.fillStyle = grad; * _context.fillRect(0, 0, this._width, this._height); */ { } } /** * Draw a line segment with the specified start and stop x,y coordinates, * using the specified paint. NOTE: since a line is always "framed", the * Style is ignored in the paint. * * @param startX The x-coordinate of the start point of the line * @param startY The y-coordinate of the start point of the line * @param paint The paint used to draw the line */ public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) { setHTML5CanvasContext(paint); /** * @j2sNative * var _canvas = null; * if (this.mBM != null) { * _canvas = this.mBM.mCachedCanvas; * // Update the bitmap cached canvas dirty flag * this.mBM.mIsCachedCanvasDirty = true; * } else { * _canvas = document.getElementById(this.activeCanvas); * } * var _context = _canvas.getContext("2d"); * _context.beginPath(); * _context.moveTo(startX,startY); * _context.lineTo(stopX,stopY); * _context.stroke(); */ {} if(DEBUG){ Log.d(TAG, "Draw line from ("+startX+","+startY+") to ("+stopX+","+stopY+")"); } } /** * Draw a series of lines. Each line is taken from 4 consecutive values * in the pts array. Thus to draw 1 line, the array must contain at least 4 * values. This is logically the same as drawing the array as follows: * drawLine(pts[0], pts[1], pts[2], pts[3]) followed by * drawLine(pts[4], pts[5], pts[6], pts[7]) and so on. * * @param pts Array of points to draw [x0 y0 x1 y1 x2 y2 ...] * @param paint The paint used to draw the points */ public void drawLines(float[] pts, Paint paint) { for (int i = 0; (i + 3) < pts.length; i += 4) { this.drawLine(pts[i], pts[i + 1], pts[i + 2], pts[i + 3], paint); } } /** * Draw the bitmap using the specified matrix. * * @param bitmap The bitmap to draw * @param matrix The matrix used to transform the bitmap when it is drawn * @param paint May be null. The paint used to draw the bitmap */ public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) { // Compute the tranform matrix Matrix tmp = new Matrix(); tmp.setConcat(this.ctm, matrix); // Draw bitmap setHTML5CanvasMatrix(tmp); drawBitmap(bitmap, 0, 0, paint); // Restore the matrix setHTML5CanvasMatrix(this.ctm); } /** * Draw the specified bitmap, with its top/left corner at (x,y), using * the specified paint, transformed by the current matrix. * * <p>Note: if the paint contains a maskfilter that generates a mask which * extends beyond the bitmap's original width/height (e.g. BlurMaskFilter), * then the bitmap will be drawn as if it were in a Shader with CLAMP mode. * Thus the color outside of the original width/height will be the edge * color replicated. * * <p>If the bitmap and canvas have different densities, this function * will take care of automatically scaling the bitmap to draw at the * same density as the canvas. * * @param bitmap The bitmap to be drawn * @param left The position of the left side of the bitmap being drawn * @param top The position of the top side of the bitmap being drawn * @param paint The paint used to draw the bitmap (may be null) */ public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) { throwIfRecycled(bitmap); if (this.activeCanvas == null && this.mBM == null) { return; } if (paint != null) { Xfermode xfermode = paint.getXfermode(); if (xfermode != null && xfermode instanceof PorterDuffXfermode) { if (xfermode.native_instance == PorterDuff.Mode.DST.nativeInt) { // for dst mode, as HTML5 doesn't support DST mode natively, // we just skip the drawing in MayLoon return; } } setHTML5CanvasContext(paint); } /** * @j2sNative * if (!bitmap.ensureCachedCanvas(false, true)) return; * // draw offscreen canvas into onscreen canvas * var _activeCanvas = null; * if (this.mBM != null) { * _activeCanvas = this.mBM.mCachedCanvas; * // Update the bitmap cached canvas dirty flag * this.mBM.mIsCachedCanvasDirty = true; * } else { * _activeCanvas = document.getElementById(this.activeCanvas); * } * var activeContext = _activeCanvas.getContext("2d"); * activeContext.drawImage(bitmap.mCachedCanvas, left, top); */ {} } /** * Draw the specified bitmap, scaling/translating automatically to fill * the destination rectangle. If the source rectangle is not null, it * specifies the subset of the bitmap to draw. * * <p>Note: if the paint contains a maskfilter that generates a mask which * extends beyond the bitmap's original width/height (e.g. BlurMaskFilter), * then the bitmap will be drawn as if it were in a Shader with CLAMP mode. * Thus the color outside of the original width/height will be the edge * color replicated. * * <p>This function <em>ignores the density associated with the bitmap</em>. * This is because the source and destination rectangle coordinate * spaces are in their respective densities, so must already have the * appropriate scaling factor applied. * * @param bitmap The bitmap to be drawn * @param src May be null. The subset of the bitmap to be drawn * @param dst The rectangle that the bitmap will be scaled/translated * to fit into * @param paint May be null. The paint used to draw the bitmap */ public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) { if (dst == null) { throw new NullPointerException(); } throwIfRecycled(bitmap); if (src == null) { src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); } if (src.isEmpty() || dst.isEmpty()) { return; } if (this.activeCanvas == null && this.mBM == null) { return; } if (paint != null) { Xfermode xfermode = paint.getXfermode(); if (xfermode != null && xfermode instanceof PorterDuffXfermode) { if (xfermode.native_instance == PorterDuff.Mode.DST.nativeInt) { // for dst mode, as HTML5 doesn't support DST mode natively, // we just skip the drawing in MayLoon return; } } setHTML5CanvasContext(paint); } /** * @j2sNative * if (!bitmap.ensureCachedCanvas(false, true)) return; * // draw offscreen canvas into onscreen canvas * var _activeCanvas = null; * if (this.mBM != null) { * _activeCanvas = this.mBM.mCachedCanvas; * // Update the bitmap cached canvas dirty flag * this.mBM.mIsCachedCanvasDirty = true; * } else { * _activeCanvas = document.getElementById(this.activeCanvas); * } * var activeContext = _activeCanvas.getContext("2d"); * activeContext.drawImage(bitmap.mCachedCanvas, src.left, src.top, src.width(), src.height(), * dst.left, dst.top, dst.width(), dst.height()); */{} } /** * Draw the specified bitmap, scaling/translating automatically to fill * the destination rectangle. If the source rectangle is not null, it * specifies the subset of the bitmap to draw. * * <p>Note: if the paint contains a maskfilter that generates a mask which * extends beyond the bitmap's original width/height (e.g. BlurMaskFilter), * then the bitmap will be drawn as if it were in a Shader with CLAMP mode. * Thus the color outside of the original width/height will be the edge * color replicated. * * <p>This function <em>ignores the density associated with the bitmap</em>. * This is because the source and destination rectangle coordinate * spaces are in their respective densities, so must already have the * appropriate scaling factor applied. * * @param bitmap The bitmap to be drawn * @param src May be null. The subset of the bitmap to be drawn * @param dst The rectangle that the bitmap will be scaled/translated * to fit into * @param paint May be null. The paint used to draw the bitmap */ public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) { if (dst == null) { throw new NullPointerException(); } throwIfRecycled(bitmap); if (src == null) { src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); } if (src.isEmpty() || dst.isEmpty()) { return; } if (this.activeCanvas == null && this.mBM == null) { return; } if (paint != null) setHTML5CanvasContext(paint); /** * @j2sNative * if (!bitmap.ensureCachedCanvas(false, true)) return; * // draw offscreen canvas into onscreen canvas * var _activeCanvas = null; * if (this.mBM != null) { * _activeCanvas = this.mBM.mCachedCanvas; * // Update the bitmap cached canvas dirty flag * this.mBM.mIsCachedCanvasDirty = true; * } else { * _activeCanvas = document.getElementById(this.activeCanvas); * } * var activeContext = _activeCanvas.getContext("2d"); * activeContext.drawImage(bitmap.mCachedCanvas, src.left, src.top, src.width(), src.height(), * dst.left, dst.top, dst.width(), dst.height()); */{} } /** * Treat the specified array of colors as a bitmap, and draw it. This gives * the same result as first creating a bitmap from the array, and then * drawing it, but this method avoids explicitly creating a bitmap object * which can be more efficient if the colors are changing often. * * @param colors Array of colors representing the pixels of the bitmap * @param offset Offset into the array of colors for the first pixel * @param stride The number of colors in the array between rows (must be * >= width or <= -width). * @param x The X coordinate for where to draw the bitmap * @param y The Y coordinate for where to draw the bitmap * @param width The width of the bitmap * @param height The height of the bitmap * @param hasAlpha True if the alpha channel of the colors contains valid * values. If false, the alpha byte is ignored (assumed to * be 0xFF for every pixel). * @param paint May be null. The paint used to draw the bitmap */ public void drawBitmap(int[] colors, int offset, int stride, float x, float y, int width, int height, boolean hasAlpha, Paint paint) { Bitmap bitmap = Bitmap.createBitmap(colors, offset, stride, width, height, Config.ARGB_8888); drawBitmap(bitmap, x, y, paint); } /** * ************************************************************* * In these functions, both fg and bg canvas need to change */ public void setDimension(int width, int height) { _width = width; _height = height; } public void translate(float dx, float dy) { _ddx += dx; _ddy += dy; if(this.mBM != null){ /** * @j2sNative * var context = this.mBM.mCachedCanvas.getContext("2d"); * context.translate(dx, dy); */{} // track the transformation in ctm this.ctm.preTranslate(dx, dy); return; } /** @j2sNative var _canvas = document.getElementById(this.activeCanvas); if (_canvas != null) { var _context = _canvas.getContext("2d"); _context.translate(dx, dy); } */{} // track the transformation in ctm this.ctm.preTranslate(dx, dy); } /** * Preconcat the current matrix with the specified scale. * * @param sx The amount to scale in X * @param sy The amount to scale in Y */ public void scale(float sx, float sy) { if(this.mBM != null){ /** * @j2sNative * var context = this.mBM.mCachedCanvas.getContext("2d"); * context.scale(sx, sy); */{} // track the transformation in ctm this.ctm.preScale(sx, sy); return; } /** @j2sNative var _canvas = document.getElementById(this.activeCanvas); var _context = _canvas.getContext("2d"); _context.scale(sx, sy); */{} // track the transformation in ctm this.ctm.preScale(sx, sy); } /** * Preconcat the current matrix with the specified scale. * * @param sx The amount to scale in X * @param sy The amount to scale in Y * @param px The x-coord for the pivot point (unchanged by the rotation) * @param py The y-coord for the pivot point (unchanged by the rotation) */ public final void scale(float sx, float sy, float px, float py) { translate(px, py); scale(sx, sy); translate(-px, -py); } public void clear(){ /** * @j2sNative * var _activeCanvas = null; * if (this.mBM != null) { * _activeCanvas = this.mBM.mCachedCanvas; * // Update the bitmap cached canvas dirty flag * this.mBM.mIsCachedCanvasDirty = true; * } else { * _activeCanvas = document.getElementById(this.activeCanvas); * } * if(!_activeCanvas){ * return; * } //MayLoon: Please refer to //http://jsperf.com/ctx-clearrect-vs-canvas-width-canvas-width/2 //http://www.html5rocks.com/en/tutorials/canvas/performance/ //for more detail about redraw a canvas //_canvas.width = _canvas.width; * var _context = _activeCanvas.getContext("2d"); * _context.clearRect(0,0,_activeCanvas.width,_activeCanvas.height); */ { } } /** * Preconcat the current matrix with the specified rotation. * * @param degrees The amount to rotate, in degrees * @param px The x-coord for the pivot point (unchanged by the rotation) * @param py The y-coord for the pivot point (unchanged by the rotation) */ public final void rotate(float degrees, float px, float py) { translate(px, py); rotate(degrees); translate(-px, -py); } /** * Preconcat the current matrix with the specified rotation. * * @param degrees The amount to rotate, in degrees */ public void rotate(float degrees) { if(this.mBM != null){ /** * @j2sNative * var context = this.mBM.mCachedCanvas.getContext("2d"); * context.rotate(degrees * Math.PI / 180); */{} // track the transformation in ctm this.ctm.preRotate(degrees); return; } /** * @j2sNative * var _canvas = document.getElementById(this.activeCanvas); * var _context = _canvas.getContext("2d"); * _context.rotate(degrees * Math.PI / 180); */{} // track the transformation in ctm this.ctm.preRotate(degrees); } void setHTML5CanvasMatrix(Matrix ctm) { float[] mt = new float[9]; ctm.getValues(mt); if (mt[Matrix.MPERSP_0] != 0 || mt[Matrix.MPERSP_1] != 0 || mt[Matrix.MPERSP_2] != 1) { Log.e(TAG, "Not support perspective matrix!"); } float MSCALE_X = mt[Matrix.MSCALE_X]; float MSKEW_Y = mt[Matrix.MSKEW_Y]; float MSKEW_X = mt[Matrix.MSKEW_X]; float MSCALE_Y = mt[Matrix.MSCALE_Y]; float MTRANS_X = mt[Matrix.MTRANS_X]; float MTRANS_Y = mt[Matrix.MTRANS_Y]; if(this.mBM != null){ /** * @j2sNative * var context = this.mBM.mCachedCanvas.getContext("2d"); * context.setTransform(MSCALE_X, MSKEW_Y, MSKEW_X, * MSCALE_Y, MTRANS_X, MTRANS_Y); */{} return; } /** * @j2sNative * var _canvas = document.getElementById(this.activeCanvas); * var _context = _canvas.getContext("2d"); * _context.setTransform(MSCALE_X, MSKEW_Y, MSKEW_X, * MSCALE_Y, MTRANS_X, MTRANS_Y); */{} } public void skew(float sx, float sy) { // track the transformation in ctm this.ctm.preSkew(sx, sy); setHTML5CanvasMatrix(this.ctm); } /** * Preconcat the current matrix with the specified matrix. * * @param matrix The matrix to preconcatenate with the current matrix */ public void concat(Matrix matrix) { // track the transformation in ctm this.ctm.preConcat(matrix); setHTML5CanvasMatrix(this.ctm); } /** * Completely replace the current matrix with the specified matrix. If the * matrix parameter is null, then the current matrix is reset to identity. * * @param matrix The matrix to replace the current matrix with. If it is * null, set the current matrix to identity. */ public void setMatrix(Matrix matrix) { // track the transformation in ctm if (matrix == null) { this.ctm.reset(); } else { this.ctm.set(matrix); } setHTML5CanvasMatrix(this.ctm); } /** * Return, in ctm, the current transformation matrix. This does not alter * the matrix in the canvas, but just returns a copy of it. */ public void getMatrix(Matrix ctm) { ctm.set(this.ctm); } /** * Return a new matrix with a copy of the canvas' current transformation * matrix. */ public final Matrix getMatrix() { Matrix m = new Matrix(); getMatrix(m); return m; } public int save() { /** * @j2sNative * var canvas = null; * if (this.mBM != null) { * canvas = this.mBM.mCachedCanvas; * } else { * canvas = document.getElementById(this.activeCanvas); * } * if (canvas != null) { * var context = canvas.getContext("2d"); * context.save(); * } */{} saveCount++; CanvasState state = new CanvasState(); state._rect = new Rect(_ddx, _ddy, _ddx + _width, _ddy + _height); state._clipBounds = new Rect(mClipBounds); state._ctm = new Matrix(this.ctm); transList.add(state); return saveCount; } /** * This behaves the same as save(), but in addition it allocates an * offscreen bitmap. All drawing calls are directed there, and only when * the balancing call to restore() is made is that offscreen transfered to * the canvas (or the previous layer). Subsequent calls to translate, * scale, rotate, skew, concat or clipRect, clipPath all operate on this * copy. When the balancing call to restore() is made, this copy is * deleted and the previous matrix/clip state is restored. * * @param bounds May be null. The maximum size the offscreen bitmap * needs to be (in local coordinates) * @param paint This is copied, and is applied to the offscreen when * restore() is called. * @param saveFlags see _SAVE_FLAG constants * @return value to pass to restoreToCount() to balance this save() */ public int saveLayer(RectF bounds, Paint paint, int saveFlags) { return save(); } /** * Helper version of saveLayer() that takes 4 values rather than a RectF. */ public int saveLayer(float left, float top, float right, float bottom, Paint paint, int saveFlags) { //HTML5 supports save() and restore() menthod only return save(); } public void restore() { if (saveCount <= 1) { throw new IllegalStateException("Underflow in restore"); } /** * @j2sNative * var canvas = null; * if (this.mBM != null) { * canvas = this.mBM.mCachedCanvas; * } else { * canvas = document.getElementById(this.activeCanvas); * } * if (canvas != null) { * var context = canvas.getContext("2d"); * context.restore(); * } */{} Rect cache = ((CanvasState) transList.get(saveCount - 1))._rect; _ddx = cache.left; _ddy = cache.top; _width = cache.right - cache.left; _height = cache.bottom - cache.top; Rect clip = ((CanvasState) transList.get(saveCount - 1))._clipBounds; this.mClipBounds.copyFrom(clip); Matrix mt = ((CanvasState) transList.get(saveCount - 1))._ctm; this.ctm.set(mt); transList.remove(saveCount - 1); saveCount--; } public int getSaveCount() { return saveCount; } public void restoreToCount(int count) { if (count < 1) { throw new IllegalArgumentException("Underflow in restoreToCount"); } while (saveCount >= count) { restore(); } } public boolean clipRect(float left, float top, float right, float bottom) { /** * @j2sNative * var canvas = null; * if (this.mBM != null) { * canvas = this.mBM.mCachedCanvas; * } else { * canvas = document.getElementById(this.activeCanvas); * } * if (canvas != null) { * var _context = canvas.getContext("2d"); * _context.beginPath(); * _context.rect(left,top,right-left,bottom-top); * _context.closePath(); * _context.clip(); * } */{} mClipBounds.set((int)left, (int)top, (int)right, (int)bottom); return true; } /** * Intersect the current clip with the specified rectangle, which is * expressed in local coordinates. * * @param rect The rectangle to intersect with the current clip. * @return true if the resulting clip is non-empty */ public boolean clipRect(Rect rect) { return this.clipRect(rect.left, rect.top, rect.right, rect.bottom); } /** * Draw the specified Rect using the specified Paint. The rectangle * will be filled or framed based on the Style in the paint. * * @param r The rectangle to be drawn. * @param paint The paint used to draw the rectangle */ public void drawRect(Rect r, Paint paint) { drawRect(r.left, r.top, r.right, r.bottom, paint); } /** * Draw the specified Rect using the specified paint. The rectangle will * be filled or framed based on the Style in the paint. * * @param rect The rect to be drawn * @param paint The paint used to draw the rect */ public void drawRect(RectF rect, Paint paint) { drawRect(rect.left, rect.top, rect.right, rect.bottom, paint); } /** * Draw the specified Rect using the specified paint. The rectangle will * be filled or framed based on the Style in the paint. * * @param left The left side of the rectangle to be drawn * @param top The top side of the rectangle to be drawn * @param right The right side of the rectangle to be drawn * @param bottom The bottom side of the rectangle to be drawn * @param paint The paint used to draw the rect */ public void drawRect(float left, float top, float right, float bottom, Paint paint) { Path path = new Path(); path.addRect(left, top, right, bottom, Direction.CW); this.drawPath(path, paint); } /** * Draw the specified circle using the specified paint. If radius is <= 0, * then nothing will be drawn. The circle will be filled or framed based * on the Style in the paint. * * @param cx The x-coordinate of the center of the cirle to be drawn * @param cy The y-coordinate of the center of the cirle to be drawn * @param radius The radius of the cirle to be drawn * @param paint The paint used to draw the circle */ public void drawCircle(float cx, float cy, float radius, Paint paint) { Path path = new Path(); path.addCircle(cx, cy, radius, Direction.CW); this.drawPath(path, paint); } /** * Draw the specified oval using the specified paint. The oval will be * filled or framed based on the Style in the paint. * * @param oval The rectangle bounds of the oval to be drawn */ public void drawOval(RectF oval, Paint paint) { if (oval == null) { throw new NullPointerException(); } Path path = new Path(); path.addOval(oval, Direction.CW); this.drawPath(path, paint); } /** * Draw the specified arc, which will be scaled to fit inside the * specified oval. If the sweep angle is >= 360, then the oval is drawn * completely. Note that this differs slightly from SkPath::arcTo, which * treats the sweep angle mod 360. * * @param oval The bounds of oval used to define the shape and size * of the arc * @param startAngle Starting angle (in degrees) where the arc begins * @param sweepAngle Sweep angle (in degrees) measured clockwise * @param useCenter If true, include the center of the oval in the arc, and close it if it is being stroked. This will draw a wedge * @param paint The paint used to draw the arc */ public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) { if (oval == null) { throw new NullPointerException(); } if (sweepAngle >= 360) { this.drawOval(oval, paint); } else { Path path = new Path(); if (useCenter) { path.moveTo(oval.centerX(), oval.centerY()); } path.arcTo(oval, startAngle, sweepAngle, !useCenter); if (useCenter) { path.close(); } this.drawPath(path, paint); } } /** * Draw the specified round-rect using the specified paint. The roundrect * will be filled or framed based on the Style in the paint. * * @param rect The rectangular bounds of the roundRect to be drawn * @param rx The x-radius of the oval used to round the corners * @param ry The y-radius of the oval used to round the corners * @param paint The paint used to draw the roundRect */ public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) { if (rect == null) { throw new NullPointerException(); } if (rx > 0 && ry > 0) { Path path = new Path(); path.addRoundRect(rect, rx, ry, Direction.CW); this.drawPath(path, paint); } else { this.drawRect(rect, paint); } } private static void throwIfRecycled(Bitmap bitmap) { if (bitmap.isRecycled()) { throw new RuntimeException( "Canvas: trying to use a recycled bitmap " + bitmap); } } /** * Draw the specified path using the specified paint. The path will be * filled or framed based on the Style in the paint. * * @param path The path to be drawn * @param paint The paint used to draw the path */ public void drawPath(Path path, Paint paint) { if (paint != null) { Xfermode xfermode = paint.getXfermode(); if (xfermode != null && xfermode instanceof PorterDuffXfermode) { if (xfermode.native_instance == PorterDuff.Mode.DST.nativeInt) { // for dst mode, as HTML5 doesn't support DST mode natively, // we just skip the drawing in MayLoon return; } } setHTML5CanvasContext(paint); } if (this.mBM != null) { path.drawOnCanvas(null, this.mBM, paint); } else { path.drawOnCanvas(this.activeCanvas, null, paint); } } /** * Draw a series of points. Each point is centered at the coordinate * specified by pts[], and its diameter is specified by the paint's stroke * width (as transformed by the canvas' CTM), with special treatment for a * stroke width of 0, which always draws exactly 1 pixel (or at most 4 if * antialiasing is enabled). The shape of the point is controlled by the * paint's Cap type. The shape is a square, unless the cap type is Round, in * which case the shape is a circle. * * @param pts Array of points to draw [x0 y0 x1 y1 x2 y2 ...] * @param offset Number of values to skip before starting to draw. * @param count The number of values to process, after skipping offset of * them. Since one point uses two values, the number of "points" * that are drawn is really (count >> 1). * @param paint The paint used to draw the points */ public void drawPoints(float[] pts, int offset, int count, Paint paint) { if ((offset | count) < 0 || offset + count > pts.length) { throw new ArrayIndexOutOfBoundsException(); } for (int i = offset; i < pts.length; i = i + 2) { drawPoint(pts[i], pts[i + 1], paint); } } /** * Helper for drawPoints() that assumes you want to draw the entire array */ public void drawPoints(float[] pts, Paint paint) { drawPoints(pts, 0, pts.length, paint); } /** * Helper for drawPoints() for drawing a single point. */ public void drawPoint(float x, float y, Paint paint) { float diameter = 1; if (paint != null) { Xfermode xfermode = paint.getXfermode(); if (xfermode != null && xfermode instanceof PorterDuffXfermode) { if (xfermode.native_instance == PorterDuff.Mode.DST.nativeInt) { // for dst mode, as HTML5 doesn't support DST mode natively, // we just skip the drawing in MayLoon return; } } setHTML5CanvasContext(paint); diameter = paint.getStrokeWidth(); } Path path = new Path(); float radius = diameter / 2; if (paint != null && paint.getStrokeCap().equals(Cap.ROUND)) { path.addCircle(x, y, radius, Direction.CW); } else { path.addRect(x - radius, y - radius, x + radius, y + radius, Direction.CW); } this.drawPath(path, paint); } public int getWidth() { /** * @j2sNative * var _canvas = null; * if (this.mBM != null) { * _canvas = this.mBM.mCachedCanvas; * } else { * _canvas = document.getElementById(this.activeCanvas); * } * if (_canvas != null) { * this._width = _canvas.width; * } else { * this._width = 0; * } */{} return _width; } public int getHeight() { /** * @j2sNative * var _canvas = null; * if (this.mBM != null) { * _canvas = this.mBM.mCachedCanvas; * } else { * _canvas = document.getElementById(this.activeCanvas); * } * if (_canvas != null) { * this._height = _canvas.width; * } else { * this._height = 0; * } */{} return _height; } /** * <p>Returns the target density of the canvas. The default density is * derived from the density of its backing bitmap, or * {@link Bitmap#DENSITY_NONE} if there is not one.</p> * * @return Returns the current target density of the canvas, which is used * to determine the scaling factor when drawing a bitmap into it. * * @see #setDensity(int) * @see Bitmap#getDensity() */ public int getDensity() { return mDensity; } public void setBitmap(Bitmap bm){ mBM = bm; if(mBM!=null){ // Ensure Cached Canvas is created for this bitmap this.mBM.ensureCachedCanvas(false, false); } } /** * Retrieve the clip bounds, returning true if they are non-empty. * * @param bounds Return the clip bounds here. If it is null, ignore it but * still return true if the current clip is non-empty. * @return true if the current clip is non-empty. */ public boolean getClipBounds(Rect bounds) { if (mClipBounds != null) { if (bounds != null) { bounds.copyFrom(mClipBounds); } return true; } return false; } /** * @j2sNative * console.log("Missing method: drawPicture"); */ @MayloonStubAnnotation() public void drawPicture(Object picture, Rect dst) { System.out.println("Stub" + " Function : drawPicture"); return; } /** * @j2sNative * console.log("Missing method: clipRegion"); */ @MayloonStubAnnotation() public boolean clipRegion(Region region, Region.Op op) { System.out.println("Stub" + " Function : clipRegion"); return true; } /** * @j2sNative * console.log("Missing method: clipRegion"); */ @MayloonStubAnnotation() public boolean clipRegion(Region region) { System.out.println("Stub" + " Function : clipRegion"); return true; } /** * @j2sNative * console.log("Missing method: finalize"); */ @MayloonStubAnnotation() protected void finalize() { System.out.println("Stub" + " Function : finalize"); return; } /** * @j2sNative * console.log("Missing method: getClipBounds"); */ @MayloonStubAnnotation() public final Rect getClipBounds() { System.out.println("Stub" + " Function : getClipBounds"); return null; } /** * @j2sNative * console.log("Missing method: setDensity"); */ @MayloonStubAnnotation() public void setDensity(int density) { System.out.println("Stub" + " Function : setDensity"); return; } public void drawPaint(Paint paint) { //System.out.println("Stub" + " Function : drawPaint"); setHTML5CanvasContext(paint); /** * @j2sNative * var _canvas = null; * if (this.mBM != null) { * _canvas = this.mBM.mCachedCanvas; * // Update the bitmap cached canvas dirty flag * this.mBM.mIsCachedCanvasDirty = true; * } else { * _canvas = document.getElementById(this.activeCanvas); * } * var _context = _canvas.getContext("2d"); * _context.fillRect(0, 0, _canvas.width, _canvas.height); */ {} return; } /** * @j2sNative * console.log("Missing method: clipRect"); */ @MayloonStubAnnotation() public boolean clipRect(RectF rect) { System.out.println("Stub" + " Function : clipRect"); return true; } /** * @j2sNative * console.log("Missing method: clipRect"); */ @MayloonStubAnnotation() public boolean clipRect(Rect rect, Region.Op op) { System.out.println("Stub" + " Function : clipRect"); return true; } /** * @j2sNative * console.log("Missing method: clipRect"); */ @MayloonStubAnnotation() public boolean clipRect(RectF rect, Region.Op op) { System.out.println("Stub" + " Function : clipRect"); return true; } /** * @j2sNative * console.log("Missing method: clipRect"); */ @MayloonStubAnnotation() public boolean clipRect(float left, float top, float right, float bottom, Region.Op op) { System.out.println("Stub" + " Function : clipRect"); return true; } /** * @j2sNative * console.log("Missing method: save"); */ @MayloonStubAnnotation() public int save(int saveFlags) { System.out.println("Stub" + " Function : save"); return 0; } /** * @j2sNative * console.log("Missing method: freeGlCaches"); */ @MayloonStubAnnotation() public static void freeGlCaches() { System.out.println("Stub" + " Function : freeGlCaches"); return; } public int saveLayerAlpha(int left, int top, int right, int bottom, int Alpha, int saveFlags) { /** * @j2sNative * var canvas = null; * if (this.mBM != null) { * canvas = this.mBM.mCachedCanvas; * } else { * canvas = document.getElementById(this.activeCanvas); * } * if (canvas != null) { * var context = canvas.getContext("2d"); * var alpha = (Alpha & 0xFF) / 255; * context.globalAlpha = alpha; * context.save(); * } */{} saveCount++; CanvasState state = new CanvasState(); _ddx = left; _ddy = top; _width = right - left; _height = bottom - top; state._rect = new Rect(_ddx, _ddy, _ddx + _width, _ddy + _height); state._clipBounds = new Rect(mClipBounds); state._ctm = new Matrix(this.ctm); transList.add(state); return saveCount; } }