blob: 94152cd73ca20bf0e4af8fc89ff85f0462784b6f [file] [log] [blame]
/*
* Copyright (C) 2010 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 com.android.ide.common.rendering.api.LayoutLog;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.layoutlib.bridge.impl.GcSnapshot;
import com.android.layoutlib.bridge.impl.PorterDuffUtility;
import com.android.ninepatch.NinePatchChunk;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import android.annotation.Nullable;
import android.graphics.Bitmap.Config;
import android.text.TextUtils;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import libcore.util.NativeAllocationRegistry_Delegate;
/**
* Delegate implementing the native methods of android.graphics.Canvas
*
* Through the layoutlib_create tool, the original native methods of Canvas have been replaced
* by calls to methods of the same name in this delegate class.
*
* This class behaves like the original native implementation, but in Java, keeping previously
* native data into its own objects and mapping them to int that are sent back and forth between
* it and the original Canvas class.
*
* @see DelegateManager
*
*/
public final class Canvas_Delegate {
// ---- delegate manager ----
private static final DelegateManager<Canvas_Delegate> sManager =
new DelegateManager<Canvas_Delegate>(Canvas_Delegate.class);
private static long sFinalizer = -1;
// ---- delegate helper data ----
private final static boolean[] sBoolOut = new boolean[1];
// ---- delegate data ----
private Bitmap_Delegate mBitmap;
private GcSnapshot mSnapshot;
private DrawFilter_Delegate mDrawFilter = null;
// ---- Public Helper methods ----
/**
* Returns the native delegate associated to a given {@link Canvas} object.
*/
public static Canvas_Delegate getDelegate(Canvas canvas) {
return sManager.getDelegate(canvas.getNativeCanvasWrapper());
}
/**
* Returns the native delegate associated to a given an int referencing a {@link Canvas} object.
*/
public static Canvas_Delegate getDelegate(long native_canvas) {
return sManager.getDelegate(native_canvas);
}
/**
* Returns the current {@link Graphics2D} used to draw.
*/
public GcSnapshot getSnapshot() {
return mSnapshot;
}
/**
* Returns the {@link DrawFilter} delegate or null if none have been set.
*
* @return the delegate or null.
*/
public DrawFilter_Delegate getDrawFilter() {
return mDrawFilter;
}
// ---- native methods ----
@LayoutlibDelegate
/*package*/ static void nFreeCaches() {
// nothing to be done here.
}
@LayoutlibDelegate
/*package*/ static void nFreeTextLayoutCaches() {
// nothing to be done here yet.
}
@LayoutlibDelegate
/*package*/ static long nInitRaster(@Nullable Bitmap bitmap) {
long nativeBitmapOrZero = 0;
if (bitmap != null) {
nativeBitmapOrZero = bitmap.getNativeInstance();
}
if (nativeBitmapOrZero > 0) {
// get the Bitmap from the int
Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmapOrZero);
// create a new Canvas_Delegate with the given bitmap and return its new native int.
Canvas_Delegate newDelegate = new Canvas_Delegate(bitmapDelegate);
return sManager.addNewDelegate(newDelegate);
}
// create a new Canvas_Delegate and return its new native int.
Canvas_Delegate newDelegate = new Canvas_Delegate();
return sManager.addNewDelegate(newDelegate);
}
@LayoutlibDelegate
public static void nSetBitmap(long canvas, Bitmap bitmap) {
Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas);
Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
if (canvasDelegate == null || bitmapDelegate==null) {
return;
}
canvasDelegate.mBitmap = bitmapDelegate;
canvasDelegate.mSnapshot = GcSnapshot.createDefaultSnapshot(bitmapDelegate);
}
@LayoutlibDelegate
public static boolean nIsOpaque(long nativeCanvas) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return false;
}
return canvasDelegate.mBitmap.getConfig() == Config.RGB_565;
}
@LayoutlibDelegate
public static void nSetHighContrastText(long nativeCanvas, boolean highContrastText){}
@LayoutlibDelegate
public static int nGetWidth(long nativeCanvas) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return 0;
}
return canvasDelegate.mBitmap.getImage().getWidth();
}
@LayoutlibDelegate
public static int nGetHeight(long nativeCanvas) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return 0;
}
return canvasDelegate.mBitmap.getImage().getHeight();
}
@LayoutlibDelegate
public static int nSave(long nativeCanvas, int saveFlags) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return 0;
}
return canvasDelegate.save(saveFlags);
}
@LayoutlibDelegate
public static int nSaveLayer(long nativeCanvas, float l,
float t, float r, float b,
long paint, int layerFlags) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return 0;
}
Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
if (paintDelegate == null) {
return 0;
}
return canvasDelegate.saveLayer(new RectF(l, t, r, b),
paintDelegate, layerFlags);
}
@LayoutlibDelegate
public static int nSaveLayerAlpha(long nativeCanvas, float l,
float t, float r, float b,
int alpha, int layerFlags) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return 0;
}
return canvasDelegate.saveLayerAlpha(new RectF(l, t, r, b), alpha, layerFlags);
}
@LayoutlibDelegate
public static void nRestore(long nativeCanvas, boolean throwOnUnderflow) {
// FIXME: implement throwOnUnderflow.
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return;
}
canvasDelegate.restore();
}
@LayoutlibDelegate
public static void nRestoreToCount(long nativeCanvas, int saveCount,
boolean throwOnUnderflow) {
// FIXME: implement throwOnUnderflow.
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return;
}
canvasDelegate.restoreTo(saveCount);
}
@LayoutlibDelegate
public static int nGetSaveCount(long nativeCanvas) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return 0;
}
return canvasDelegate.getSnapshot().size();
}
@LayoutlibDelegate
public static void nTranslate(long nativeCanvas, float dx, float dy) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return;
}
canvasDelegate.getSnapshot().translate(dx, dy);
}
@LayoutlibDelegate
public static void nScale(long nativeCanvas, float sx, float sy) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return;
}
canvasDelegate.getSnapshot().scale(sx, sy);
}
@LayoutlibDelegate
public static void nRotate(long nativeCanvas, float degrees) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return;
}
canvasDelegate.getSnapshot().rotate(Math.toRadians(degrees));
}
@LayoutlibDelegate
public static void nSkew(long nativeCanvas, float kx, float ky) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return;
}
// get the current top graphics2D object.
GcSnapshot g = canvasDelegate.getSnapshot();
// get its current matrix
AffineTransform currentTx = g.getTransform();
// get the AffineTransform for the given skew.
float[] mtx = Matrix_Delegate.getSkew(kx, ky);
AffineTransform matrixTx = Matrix_Delegate.getAffineTransform(mtx);
// combine them so that the given matrix is applied after.
currentTx.preConcatenate(matrixTx);
// give it to the graphics2D as a new matrix replacing all previous transform
g.setTransform(currentTx);
}
@LayoutlibDelegate
public static void nConcat(long nCanvas, long nMatrix) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
if (canvasDelegate == null) {
return;
}
Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
if (matrixDelegate == null) {
return;
}
// get the current top graphics2D object.
GcSnapshot snapshot = canvasDelegate.getSnapshot();
// get its current matrix
AffineTransform currentTx = snapshot.getTransform();
// get the AffineTransform of the given matrix
AffineTransform matrixTx = matrixDelegate.getAffineTransform();
// combine them so that the given matrix is applied after.
currentTx.concatenate(matrixTx);
// give it to the graphics2D as a new matrix replacing all previous transform
snapshot.setTransform(currentTx);
}
@LayoutlibDelegate
public static void nSetMatrix(long nCanvas, long nMatrix) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
if (canvasDelegate == null) {
return;
}
Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
if (matrixDelegate == null) {
return;
}
// get the current top graphics2D object.
GcSnapshot snapshot = canvasDelegate.getSnapshot();
// get the AffineTransform of the given matrix
AffineTransform matrixTx = matrixDelegate.getAffineTransform();
// give it to the graphics2D as a new matrix replacing all previous transform
snapshot.setTransform(matrixTx);
if (matrixDelegate.hasPerspective()) {
assert false;
Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE,
"android.graphics.Canvas#setMatrix(android.graphics.Matrix) only " +
"supports affine transformations.", null, null /*data*/);
}
}
@LayoutlibDelegate
public static boolean nClipRect(long nCanvas,
float left, float top,
float right, float bottom,
int regionOp) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
if (canvasDelegate == null) {
return false;
}
return canvasDelegate.clipRect(left, top, right, bottom, regionOp);
}
@LayoutlibDelegate
public static boolean nClipPath(long nativeCanvas,
long nativePath,
int regionOp) {
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return true;
}
Path_Delegate pathDelegate = Path_Delegate.getDelegate(nativePath);
if (pathDelegate == null) {
return true;
}
return canvasDelegate.mSnapshot.clip(pathDelegate.getJavaShape(), regionOp);
}
@LayoutlibDelegate
public static boolean nClipRegion(long nativeCanvas,
long nativeRegion,
int regionOp) {
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return true;
}
Region_Delegate region = Region_Delegate.getDelegate(nativeRegion);
if (region == null) {
return true;
}
return canvasDelegate.mSnapshot.clip(region.getJavaArea(), regionOp);
}
@LayoutlibDelegate
public static void nSetDrawFilter(long nativeCanvas, long nativeFilter) {
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return;
}
canvasDelegate.mDrawFilter = DrawFilter_Delegate.getDelegate(nativeFilter);
if (canvasDelegate.mDrawFilter != null && !canvasDelegate.mDrawFilter.isSupported()) {
Bridge.getLog().fidelityWarning(LayoutLog.TAG_DRAWFILTER,
canvasDelegate.mDrawFilter.getSupportMessage(), null, null /*data*/);
}
}
@LayoutlibDelegate
public static boolean nGetClipBounds(long nativeCanvas,
Rect bounds) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return false;
}
Rectangle rect = canvasDelegate.getSnapshot().getClip().getBounds();
if (rect != null && !rect.isEmpty()) {
bounds.left = rect.x;
bounds.top = rect.y;
bounds.right = rect.x + rect.width;
bounds.bottom = rect.y + rect.height;
return true;
}
return false;
}
@LayoutlibDelegate
public static void nGetCTM(long canvas, long matrix) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas);
if (canvasDelegate == null) {
return;
}
Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
if (matrixDelegate == null) {
return;
}
AffineTransform transform = canvasDelegate.getSnapshot().getTransform();
matrixDelegate.set(Matrix_Delegate.makeValues(transform));
}
@LayoutlibDelegate
public static boolean nQuickReject(long nativeCanvas, long path) {
// FIXME properly implement quickReject
return false;
}
@LayoutlibDelegate
public static boolean nQuickReject(long nativeCanvas,
float left, float top,
float right, float bottom) {
// FIXME properly implement quickReject
return false;
}
@LayoutlibDelegate
public static void nDrawColor(long nativeCanvas, final int color, final int mode) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return;
}
final int w = canvasDelegate.mBitmap.getImage().getWidth();
final int h = canvasDelegate.mBitmap.getImage().getHeight();
draw(nativeCanvas, new GcSnapshot.Drawable() {
@Override
public void draw(Graphics2D graphics, Paint_Delegate paint) {
// reset its transform just in case
graphics.setTransform(new AffineTransform());
// set the color
graphics.setColor(new Color(color, true /*alpha*/));
Composite composite = PorterDuffUtility.getComposite(
PorterDuffUtility.getPorterDuffMode(mode), 0xFF);
if (composite != null) {
graphics.setComposite(composite);
}
graphics.fillRect(0, 0, w, h);
}
});
}
@LayoutlibDelegate
public static void nDrawPaint(long nativeCanvas, long paint) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
"Canvas.drawPaint is not supported.", null, null /*data*/);
}
@LayoutlibDelegate
public static void nDrawPoint(long nativeCanvas, float x, float y,
long nativePaint) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
"Canvas.drawPoint is not supported.", null, null /*data*/);
}
@LayoutlibDelegate
public static void nDrawPoints(long nativeCanvas, float[] pts, int offset, int count,
long nativePaint) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
"Canvas.drawPoint is not supported.", null, null /*data*/);
}
@LayoutlibDelegate
public static void nDrawLine(long nativeCanvas,
final float startX, final float startY, final float stopX, final float stopY,
long paint) {
draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
new GcSnapshot.Drawable() {
@Override
public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY);
}
});
}
@LayoutlibDelegate
public static void nDrawLines(long nativeCanvas,
final float[] pts, final int offset, final int count,
long nativePaint) {
draw(nativeCanvas, nativePaint, false /*compositeOnly*/,
false /*forceSrcMode*/, new GcSnapshot.Drawable() {
@Override
public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
for (int i = 0; i < count; i += 4) {
graphics.drawLine((int) pts[i + offset], (int) pts[i + offset + 1],
(int) pts[i + offset + 2], (int) pts[i + offset + 3]);
}
}
});
}
@LayoutlibDelegate
public static void nDrawRect(long nativeCanvas,
final float left, final float top, final float right, final float bottom, long paint) {
draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
new GcSnapshot.Drawable() {
@Override
public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
int style = paintDelegate.getStyle();
// draw
if (style == Paint.Style.FILL.nativeInt ||
style == Paint.Style.FILL_AND_STROKE.nativeInt) {
graphics.fillRect((int)left, (int)top,
(int)(right-left), (int)(bottom-top));
}
if (style == Paint.Style.STROKE.nativeInt ||
style == Paint.Style.FILL_AND_STROKE.nativeInt) {
graphics.drawRect((int)left, (int)top,
(int)(right-left), (int)(bottom-top));
}
}
});
}
@LayoutlibDelegate
public static void nDrawOval(long nativeCanvas, final float left,
final float top, final float right, final float bottom, long paint) {
if (right > left && bottom > top) {
draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
new GcSnapshot.Drawable() {
@Override
public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
int style = paintDelegate.getStyle();
// draw
if (style == Paint.Style.FILL.nativeInt ||
style == Paint.Style.FILL_AND_STROKE.nativeInt) {
graphics.fillOval((int)left, (int)top,
(int)(right - left), (int)(bottom - top));
}
if (style == Paint.Style.STROKE.nativeInt ||
style == Paint.Style.FILL_AND_STROKE.nativeInt) {
graphics.drawOval((int)left, (int)top,
(int)(right - left), (int)(bottom - top));
}
}
});
}
}
@LayoutlibDelegate
public static void nDrawCircle(long nativeCanvas,
float cx, float cy, float radius, long paint) {
nDrawOval(nativeCanvas,
cx - radius, cy - radius, cx + radius, cy + radius,
paint);
}
@LayoutlibDelegate
public static void nDrawArc(long nativeCanvas,
final float left, final float top, final float right, final float bottom,
final float startAngle, final float sweep,
final boolean useCenter, long paint) {
if (right > left && bottom > top) {
draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
new GcSnapshot.Drawable() {
@Override
public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
int style = paintDelegate.getStyle();
Arc2D.Float arc = new Arc2D.Float(
left, top, right - left, bottom - top,
-startAngle, -sweep,
useCenter ? Arc2D.PIE : Arc2D.OPEN);
// draw
if (style == Paint.Style.FILL.nativeInt ||
style == Paint.Style.FILL_AND_STROKE.nativeInt) {
graphics.fill(arc);
}
if (style == Paint.Style.STROKE.nativeInt ||
style == Paint.Style.FILL_AND_STROKE.nativeInt) {
graphics.draw(arc);
}
}
});
}
}
@LayoutlibDelegate
public static void nDrawRoundRect(long nativeCanvas,
final float left, final float top, final float right, final float bottom,
final float rx, final float ry, long paint) {
draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
new GcSnapshot.Drawable() {
@Override
public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
int style = paintDelegate.getStyle();
// draw
if (style == Paint.Style.FILL.nativeInt ||
style == Paint.Style.FILL_AND_STROKE.nativeInt) {
graphics.fillRoundRect(
(int)left, (int)top,
(int)(right - left), (int)(bottom - top),
2 * (int)rx, 2 * (int)ry);
}
if (style == Paint.Style.STROKE.nativeInt ||
style == Paint.Style.FILL_AND_STROKE.nativeInt) {
graphics.drawRoundRect(
(int)left, (int)top,
(int)(right - left), (int)(bottom - top),
2 * (int)rx, 2 * (int)ry);
}
}
});
}
@LayoutlibDelegate
public static void nDrawPath(long nativeCanvas, long path, long paint) {
final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path);
if (pathDelegate == null) {
return;
}
draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
new GcSnapshot.Drawable() {
@Override
public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
Shape shape = pathDelegate.getJavaShape();
Rectangle2D bounds = shape.getBounds2D();
if (bounds.isEmpty()) {
// Apple JRE 1.6 doesn't like drawing empty shapes.
// http://b.android.com/178278
if (pathDelegate.isEmpty()) {
// This means that the path doesn't have any lines or curves so
// nothing to draw.
return;
}
// The stroke width is not consider for the size of the bounds so,
// for example, a horizontal line, would be considered as an empty
// rectangle.
// If the strokeWidth is not 0, we use it to consider the size of the
// path as well.
float strokeWidth = paintDelegate.getStrokeWidth();
if (strokeWidth <= 0.0f) {
return;
}
bounds.setRect(bounds.getX(), bounds.getY(),
Math.max(strokeWidth, bounds.getWidth()),
Math.max(strokeWidth, bounds.getHeight()));
}
int style = paintDelegate.getStyle();
if (style == Paint.Style.FILL.nativeInt ||
style == Paint.Style.FILL_AND_STROKE.nativeInt) {
graphics.fill(shape);
}
if (style == Paint.Style.STROKE.nativeInt ||
style == Paint.Style.FILL_AND_STROKE.nativeInt) {
graphics.draw(shape);
}
}
});
}
@LayoutlibDelegate
public static void nDrawRegion(long nativeCanvas, long nativeRegion,
long nativePaint) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
"Some canvas paths may not be drawn", null, null);
}
@LayoutlibDelegate
public static void nDrawNinePatch(Canvas thisCanvas, long nativeCanvas,
long nativeBitmap, long ninePatch, final float dstLeft, final float dstTop,
final float dstRight, final float dstBottom, long nativePaintOrZero,
final int screenDensity, final int bitmapDensity) {
// get the delegate from the native int.
final Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmap);
if (bitmapDelegate == null) {
return;
}
byte[] c = NinePatch_Delegate.getChunk(ninePatch);
if (c == null) {
// not a 9-patch?
BufferedImage image = bitmapDelegate.getImage();
drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, 0, 0, image.getWidth(),
image.getHeight(), (int) dstLeft, (int) dstTop, (int) dstRight,
(int) dstBottom);
return;
}
final NinePatchChunk chunkObject = NinePatch_Delegate.getChunk(c);
assert chunkObject != null;
if (chunkObject == null) {
return;
}
Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return;
}
// this one can be null
Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() {
@Override
public void draw(Graphics2D graphics, Paint_Delegate paint) {
chunkObject.draw(bitmapDelegate.getImage(), graphics, (int) dstLeft, (int) dstTop,
(int) (dstRight - dstLeft), (int) (dstBottom - dstTop), screenDensity,
bitmapDensity);
}
}, paintDelegate, true, false);
}
@LayoutlibDelegate
public static void nDrawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap,
float left, float top,
long nativePaintOrZero,
int canvasDensity,
int screenDensity,
int bitmapDensity) {
// get the delegate from the native int.
Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
if (bitmapDelegate == null) {
return;
}
BufferedImage image = bitmapDelegate.getImage();
float right = left + image.getWidth();
float bottom = top + image.getHeight();
drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
0, 0, image.getWidth(), image.getHeight(),
(int)left, (int)top, (int)right, (int)bottom);
}
@LayoutlibDelegate
public static void nDrawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap,
float srcLeft, float srcTop, float srcRight, float srcBottom,
float dstLeft, float dstTop, float dstRight, float dstBottom,
long nativePaintOrZero, int screenDensity, int bitmapDensity) {
// get the delegate from the native int.
Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
if (bitmapDelegate == null) {
return;
}
drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
(int)srcLeft, (int)srcTop, (int)srcRight, (int)srcBottom,
(int)dstLeft, (int)dstTop, (int)dstRight, (int)dstBottom);
}
@LayoutlibDelegate
public static void nDrawBitmap(long nativeCanvas, int[] colors,
int offset, int stride, final float x,
final float y, int width, int height,
boolean hasAlpha,
long nativePaintOrZero) {
// create a temp BufferedImage containing the content.
final BufferedImage image = new BufferedImage(width, height,
hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
image.setRGB(0, 0, width, height, colors, offset, stride);
draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, false /*forceSrcMode*/,
new GcSnapshot.Drawable() {
@Override
public void draw(Graphics2D graphics, Paint_Delegate paint) {
if (paint != null && paint.isFilterBitmap()) {
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
}
graphics.drawImage(image, (int) x, (int) y, null);
}
});
}
@LayoutlibDelegate
public static void nativeDrawBitmapMatrix(long nCanvas, Bitmap bitmap,
long nMatrix, long nPaint) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
if (canvasDelegate == null) {
return;
}
// get the delegate from the native int, which can be null
Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
// get the delegate from the native int.
Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
if (bitmapDelegate == null) {
return;
}
final BufferedImage image = getImageToDraw(bitmapDelegate, paintDelegate, sBoolOut);
Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
if (matrixDelegate == null) {
return;
}
final AffineTransform mtx = matrixDelegate.getAffineTransform();
canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() {
@Override
public void draw(Graphics2D graphics, Paint_Delegate paint) {
if (paint != null && paint.isFilterBitmap()) {
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
}
//FIXME add support for canvas, screen and bitmap densities.
graphics.drawImage(image, mtx, null);
}
}, paintDelegate, true /*compositeOnly*/, false /*forceSrcMode*/);
}
@LayoutlibDelegate
public static void nativeDrawBitmapMesh(long nCanvas, Bitmap bitmap,
int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors,
int colorOffset, long nPaint) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
"Canvas.drawBitmapMesh is not supported.", null, null /*data*/);
}
@LayoutlibDelegate
public static void nativeDrawVertices(long nCanvas, int mode, int n,
float[] verts, int vertOffset,
float[] texs, int texOffset,
int[] colors, int colorOffset,
short[] indices, int indexOffset,
int indexCount, long nPaint) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
"Canvas.drawVertices is not supported.", null, null /*data*/);
}
@LayoutlibDelegate
public static void nDrawText(long nativeCanvas, char[] text, int index, int count,
float startX, float startY, int flags, long paint, long typeface) {
drawText(nativeCanvas, text, index, count, startX, startY, (flags & 1) != 0,
paint, typeface);
}
@LayoutlibDelegate
public static void nDrawText(long nativeCanvas, String text,
int start, int end, float x, float y, final int flags, long paint,
long typeface) {
int count = end - start;
char[] buffer = TemporaryBuffer.obtain(count);
TextUtils.getChars(text, start, end, buffer, 0);
nDrawText(nativeCanvas, buffer, 0, count, x, y, flags, paint, typeface);
}
@LayoutlibDelegate
public static void nDrawTextRun(long nativeCanvas, String text,
int start, int end, int contextStart, int contextEnd,
float x, float y, boolean isRtl, long paint, long typeface) {
int count = end - start;
char[] buffer = TemporaryBuffer.obtain(count);
TextUtils.getChars(text, start, end, buffer, 0);
drawText(nativeCanvas, buffer, 0, count, x, y, isRtl, paint, typeface);
}
@LayoutlibDelegate
public static void nDrawTextRun(long nativeCanvas, char[] text,
int start, int count, int contextStart, int contextCount,
float x, float y, boolean isRtl, long paint, long typeface) {
drawText(nativeCanvas, text, start, count, x, y, isRtl, paint, typeface);
}
@LayoutlibDelegate
public static void nDrawTextOnPath(long nativeCanvas,
char[] text, int index,
int count, long path,
float hOffset,
float vOffset, int bidiFlags,
long paint, long typeface) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
"Canvas.drawTextOnPath is not supported.", null, null /*data*/);
}
@LayoutlibDelegate
public static void nDrawTextOnPath(long nativeCanvas,
String text, long path,
float hOffset,
float vOffset,
int bidiFlags, long paint,
long typeface) {
// FIXME
Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
"Canvas.drawTextOnPath is not supported.", null, null /*data*/);
}
@LayoutlibDelegate
/*package*/ static long getNativeFinalizer() {
synchronized (Canvas_Delegate.class) {
if (sFinalizer == -1) {
sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(nativePtr -> {
Canvas_Delegate delegate = sManager.getDelegate(nativePtr);
if (delegate != null) {
delegate.dispose();
}
sManager.removeJavaReferenceFor(nativePtr);
});
}
}
return sFinalizer;
}
// ---- Private delegate/helper methods ----
/**
* Executes a {@link GcSnapshot.Drawable} with a given canvas and paint.
* <p>Note that the drawable may actually be executed several times if there are
* layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}.
*/
private static void draw(long nCanvas, long nPaint, boolean compositeOnly, boolean forceSrcMode,
GcSnapshot.Drawable drawable) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
if (canvasDelegate == null) {
return;
}
// get the paint which can be null if nPaint is 0;
Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly, forceSrcMode);
}
/**
* Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided
* to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}.
* <p>Note that the drawable may actually be executed several times if there are
* layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}.
*/
private static void draw(long nCanvas, GcSnapshot.Drawable drawable) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
if (canvasDelegate == null) {
return;
}
canvasDelegate.mSnapshot.draw(drawable);
}
private static void drawText(long nativeCanvas, final char[] text, final int index,
final int count, final float startX, final float startY, final boolean isRtl,
long paint, final long typeface) {
draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
new GcSnapshot.Drawable() {
@Override
public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
// WARNING: the logic in this method is similar to Paint_Delegate.measureText.
// Any change to this method should be reflected in Paint.measureText
// assert that the typeface passed is actually the one stored in paint.
assert (typeface == paintDelegate.mNativeTypeface);
// Paint.TextAlign indicates how the text is positioned relative to X.
// LEFT is the default and there's nothing to do.
float x = startX;
int limit = index + count;
if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) {
RectF bounds = paintDelegate.measureText(text, index, count, null, 0,
isRtl);
float m = bounds.right - bounds.left;
if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) {
x -= m / 2;
} else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
x -= m;
}
}
new BidiRenderer(graphics, paintDelegate, text).setRenderLocation(x, startY)
.renderText(index, limit, isRtl, null, 0, true);
}
});
}
private Canvas_Delegate(Bitmap_Delegate bitmap) {
mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap);
}
private Canvas_Delegate() {
mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/);
}
/**
* Disposes of the {@link Graphics2D} stack.
*/
private void dispose() {
mSnapshot.dispose();
}
private int save(int saveFlags) {
// get the current save count
int count = mSnapshot.size();
mSnapshot = mSnapshot.save(saveFlags);
// return the old save count
return count;
}
private int saveLayerAlpha(RectF rect, int alpha, int saveFlags) {
Paint_Delegate paint = new Paint_Delegate();
paint.setAlpha(alpha);
return saveLayer(rect, paint, saveFlags);
}
private int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) {
// get the current save count
int count = mSnapshot.size();
mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags);
// return the old save count
return count;
}
/**
* Restores the {@link GcSnapshot} to <var>saveCount</var>
* @param saveCount the saveCount
*/
private void restoreTo(int saveCount) {
mSnapshot = mSnapshot.restoreTo(saveCount);
}
/**
* Restores the top {@link GcSnapshot}
*/
private void restore() {
mSnapshot = mSnapshot.restore();
}
private boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
return mSnapshot.clipRect(left, top, right, bottom, regionOp);
}
private static void drawBitmap(
long nativeCanvas,
Bitmap_Delegate bitmap,
long nativePaintOrZero,
final int sleft, final int stop, final int sright, final int sbottom,
final int dleft, final int dtop, final int dright, final int dbottom) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
return;
}
// get the paint, which could be null if the int is 0
Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
final BufferedImage image = getImageToDraw(bitmap, paintDelegate, sBoolOut);
draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0],
new GcSnapshot.Drawable() {
@Override
public void draw(Graphics2D graphics, Paint_Delegate paint) {
if (paint != null && paint.isFilterBitmap()) {
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
}
//FIXME add support for canvas, screen and bitmap densities.
graphics.drawImage(image, dleft, dtop, dright, dbottom,
sleft, stop, sright, sbottom, null);
}
});
}
/**
* Returns a BufferedImage ready for drawing, based on the bitmap and paint delegate.
* The image returns, through a 1-size boolean array, whether the drawing code should
* use a SRC composite no matter what the paint says.
*
* @param bitmap the bitmap
* @param paint the paint that will be used to draw
* @param forceSrcMode whether the composite will have to be SRC
* @return the image to draw
*/
private static BufferedImage getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint,
boolean[] forceSrcMode) {
BufferedImage image = bitmap.getImage();
forceSrcMode[0] = false;
// if the bitmap config is alpha_8, then we erase all color value from it
// before drawing it.
if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) {
fixAlpha8Bitmap(image);
} else if (!bitmap.hasAlpha()) {
// hasAlpha is merely a rendering hint. There can in fact be alpha values
// in the bitmap but it should be ignored at drawing time.
// There is two ways to do this:
// - override the composite to be SRC. This can only be used if the composite
// was going to be SRC or SRC_OVER in the first place
// - Create a different bitmap to draw in which all the alpha channel values is set
// to 0xFF.
if (paint != null) {
Xfermode_Delegate xfermodeDelegate = paint.getXfermode();
if (xfermodeDelegate instanceof PorterDuffXfermode_Delegate) {
PorterDuff.Mode mode =
((PorterDuffXfermode_Delegate)xfermodeDelegate).getMode();
forceSrcMode[0] = mode == PorterDuff.Mode.SRC_OVER ||
mode == PorterDuff.Mode.SRC;
}
}
// if we can't force SRC mode, then create a temp bitmap of TYPE_RGB
if (!forceSrcMode[0]) {
image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF);
}
}
return image;
}
private static void fixAlpha8Bitmap(final BufferedImage image) {
int w = image.getWidth();
int h = image.getHeight();
int[] argb = new int[w * h];
image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
final int length = argb.length;
for (int i = 0 ; i < length; i++) {
argb[i] &= 0xFF000000;
}
image.setRGB(0, 0, w, h, argb, 0, w);
}
}