Merge "LayoutLib: support for layers."

This commit is contained in:
Xavier Ducrohet
2010-12-23 00:09:07 -08:00
committed by Android (Google) Code Review
12 changed files with 911 additions and 581 deletions

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<com.android.test.hwui.LayersActivity.LayersView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
</com.android.test.hwui.LayersActivity.LayersView>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<com.android.test.hwui.NewLayersActivity.LayersView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
</com.android.test.hwui.NewLayersActivity.LayersView>

View File

@@ -33,11 +33,11 @@ public class LayersActivity extends Activity {
setContentView(new LayersView(this));
}
static class LayersView extends View {
public static class LayersView extends View {
private Paint mLayerPaint;
private final Paint mRectPaint;
LayersView(Context c) {
public LayersView(Context c) {
super(c);
mLayerPaint = new Paint();
@@ -47,11 +47,11 @@ public class LayersActivity extends Activity {
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(140.0f, 100.0f);
//canvas.drawRGB(255, 255, 255);
int count = canvas.saveLayer(0.0f, 0.0f, 200.0f, 100.0f, mLayerPaint,
Canvas.ALL_SAVE_FLAG);
@@ -59,9 +59,9 @@ public class LayersActivity extends Activity {
canvas.drawRect(0.0f, 0.0f, 200.0f, 100.0f, mRectPaint);
canvas.restoreToCount(count);
canvas.translate(0.0f, 125.0f);
count = canvas.saveLayer(0.0f, 0.0f, 200.0f, 100.0f, mLayerPaint,
Canvas.ALL_SAVE_FLAG);
@@ -75,8 +75,8 @@ public class LayersActivity extends Activity {
mRectPaint.setColor(0xff0000ff);
mRectPaint.setAlpha(255);
canvas.drawRect(0.0f, 0.0f, 100.0f, 50.0f, mRectPaint);
canvas.drawRect(0.0f, 0.0f, 100.0f, 50.0f, mRectPaint);
mLayerPaint.setAlpha(127);
mLayerPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
count = canvas.saveLayer(50.0f, 25.0f, 150.0f, 75.0f, mLayerPaint,
@@ -86,13 +86,13 @@ public class LayersActivity extends Activity {
canvas.drawRect(50.0f, 25.0f, 150.0f, 75.0f, mRectPaint);
canvas.restoreToCount(count);
canvas.translate(0.0f, 125.0f);
mRectPaint.setColor(0xff0000ff);
mRectPaint.setAlpha(255);
canvas.drawRect(0.0f, 0.0f, 100.0f, 50.0f, mRectPaint);
canvas.drawRect(0.0f, 0.0f, 100.0f, 50.0f, mRectPaint);
mLayerPaint.setColor(0xffff0000);
mLayerPaint.setAlpha(127);
mLayerPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
@@ -103,7 +103,7 @@ public class LayersActivity extends Activity {
canvas.drawRect(50.0f, 25.0f, 150.0f, 75.0f, mRectPaint);
canvas.restoreToCount(count);
mLayerPaint = new Paint();
}
}

View File

@@ -31,11 +31,11 @@ public class NewLayersActivity extends Activity {
setContentView(new LayersView(this));
}
static class LayersView extends View {
public static class LayersView extends View {
private Paint mLayerPaint;
private final Paint mRectPaint;
LayersView(Context c) {
public LayersView(Context c) {
super(c);
mLayerPaint = new Paint();
@@ -57,7 +57,7 @@ public class NewLayersActivity extends Activity {
canvas.translate(0.0f, 200.0f);
drawStuff(canvas, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
canvas.restore();
}

View File

@@ -23,14 +23,12 @@ import com.android.layoutlib.bridge.impl.GcSnapshot;
import android.graphics.Paint_Delegate.FontInfo;
import android.text.TextUtils;
import java.awt.BasicStroke;
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.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.List;
@@ -57,13 +55,9 @@ public final class Canvas_Delegate {
// ---- delegate helper data ----
private interface Drawable {
void draw(Graphics2D graphics, Paint_Delegate paint);
}
// ---- delegate data ----
private BufferedImage mBufferedImage;
private GcSnapshot mSnapshot = new GcSnapshot();
private GcSnapshot mSnapshot;
// ---- Public Helper methods ----
@@ -84,7 +78,7 @@ public final class Canvas_Delegate {
/**
* Returns the current {@link Graphics2D} used to draw.
*/
public GcSnapshot getGcSnapshot() {
public GcSnapshot getSnapshot() {
return mSnapshot;
}
@@ -125,7 +119,7 @@ public final class Canvas_Delegate {
return;
}
canvasDelegate.getGcSnapshot().translate(dx, dy);
canvasDelegate.getSnapshot().translate(dx, dy);
}
/*package*/ static void rotate(Canvas thisCanvas, float degrees) {
@@ -136,7 +130,7 @@ public final class Canvas_Delegate {
return;
}
canvasDelegate.getGcSnapshot().rotate(Math.toRadians(degrees));
canvasDelegate.getSnapshot().rotate(Math.toRadians(degrees));
}
/*package*/ static void scale(Canvas thisCanvas, float sx, float sy) {
@@ -147,7 +141,7 @@ public final class Canvas_Delegate {
return;
}
canvasDelegate.getGcSnapshot().scale(sx, sy);
canvasDelegate.getSnapshot().scale(sx, sy);
}
/*package*/ static void skew(Canvas thisCanvas, float kx, float ky) {
@@ -159,7 +153,7 @@ public final class Canvas_Delegate {
}
// get the current top graphics2D object.
GcSnapshot g = canvasDelegate.getGcSnapshot();
GcSnapshot g = canvasDelegate.getSnapshot();
// get its current matrix
AffineTransform currentTx = g.getTransform();
@@ -235,7 +229,7 @@ public final class Canvas_Delegate {
return 0;
}
return canvasDelegate.getGcSnapshot().size();
return canvasDelegate.getSnapshot().size();
}
/*package*/ static void restoreToCount(Canvas thisCanvas, int saveCount) {
@@ -260,33 +254,18 @@ public final class Canvas_Delegate {
throw new UnsupportedOperationException();
}
/*package*/ static void drawLines(Canvas thisCanvas, float[] pts, int offset, int count,
/*package*/ static void drawLines(Canvas thisCanvas,
final float[] pts, final int offset, final int count,
Paint paint) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
if (canvasDelegate == null) {
assert false;
return;
}
Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint.mNativePaint);
if (paintDelegate == null) {
assert false;
return;
}
// get a Graphics2D object configured with the drawing parameters.
Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
try {
for (int i = 0 ; i < count ; i += 4) {
g.drawLine((int)pts[i + offset], (int)pts[i + offset + 1],
(int)pts[i + offset + 2], (int)pts[i + offset + 3]);
}
} finally {
// dispose Graphics2D object
g.dispose();
}
draw(thisCanvas.mNativeCanvas, paint.mNativePaint, false /*compositeOnly*/,
new GcSnapshot.Drawable() {
public void draw(Graphics2D graphics, Paint_Delegate paint) {
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]);
}
}
});
}
/*package*/ static void freeCaches() {
@@ -331,29 +310,66 @@ public final class Canvas_Delegate {
/*package*/ static int native_saveLayer(int nativeCanvas, RectF bounds,
int paint, int layerFlags) {
// FIXME
throw new UnsupportedOperationException();
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
assert false;
return 0;
}
Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
if (paintDelegate == null) {
assert false;
return 0;
}
return canvasDelegate.saveLayer(bounds, paintDelegate, layerFlags);
}
/*package*/ static int native_saveLayer(int nativeCanvas, float l,
float t, float r, float b,
int paint, int layerFlags) {
// FIXME
throw new UnsupportedOperationException();
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
assert false;
return 0;
}
Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
if (paintDelegate == null) {
assert false;
return 0;
}
return canvasDelegate.saveLayer(new RectF(l, t, r, b),
paintDelegate, layerFlags);
}
/*package*/ static int native_saveLayerAlpha(int nativeCanvas,
RectF bounds, int alpha,
int layerFlags) {
// FIXME
throw new UnsupportedOperationException();
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
assert false;
return 0;
}
return canvasDelegate.saveLayerAlpha(bounds, alpha, layerFlags);
}
/*package*/ static int native_saveLayerAlpha(int nativeCanvas, float l,
float t, float r, float b,
int alpha, int layerFlags) {
// FIXME
throw new UnsupportedOperationException();
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
assert false;
return 0;
}
return canvasDelegate.saveLayerAlpha(new RectF(l, t, r, b), alpha, layerFlags);
}
@@ -372,7 +388,7 @@ public final class Canvas_Delegate {
}
// get the current top graphics2D object.
GcSnapshot snapshot = canvasDelegate.getGcSnapshot();
GcSnapshot snapshot = canvasDelegate.getSnapshot();
// get its current matrix
AffineTransform currentTx = snapshot.getTransform();
@@ -399,7 +415,7 @@ public final class Canvas_Delegate {
}
// get the current top graphics2D object.
GcSnapshot snapshot = canvasDelegate.getGcSnapshot();
GcSnapshot snapshot = canvasDelegate.getSnapshot();
// get the AffineTransform of the given matrix
AffineTransform matrixTx = matrixDelegate.getAffineTransform();
@@ -458,7 +474,7 @@ public final class Canvas_Delegate {
return false;
}
Rectangle rect = canvasDelegate.getGcSnapshot().getClip().getBounds();
Rectangle rect = canvasDelegate.getSnapshot().getClip().getBounds();
if (rect != null) {
bounds.left = rect.x;
bounds.top = rect.y;
@@ -511,7 +527,7 @@ public final class Canvas_Delegate {
native_drawColor(nativeCanvas, color, PorterDuff.Mode.SRC_OVER.nativeInt);
}
/*package*/ static void native_drawColor(int nativeCanvas, int color, int mode) {
/*package*/ static void native_drawColor(int nativeCanvas, final int color, final int mode) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
@@ -519,27 +535,26 @@ public final class Canvas_Delegate {
return;
}
// get a new graphics context.
Graphics2D graphics = (Graphics2D)canvasDelegate.getGcSnapshot().create();
try {
// reset its transform just in case
graphics.setTransform(new AffineTransform());
final int w = canvasDelegate.mBufferedImage.getWidth();
final int h = canvasDelegate.mBufferedImage.getHeight();
draw(nativeCanvas, new GcSnapshot.Drawable() {
// set the color
graphics.setColor(new Color(color, true /*alpha*/));
public void draw(Graphics2D graphics, Paint_Delegate paint) {
// reset its transform just in case
graphics.setTransform(new AffineTransform());
Composite composite = PorterDuffXfermode_Delegate.getComposite(
PorterDuffXfermode_Delegate.getPorterDuffMode(mode), 0xFF);
if (composite != null) {
graphics.setComposite(composite);
// set the color
graphics.setColor(new Color(color, true /*alpha*/));
Composite composite = PorterDuffXfermode_Delegate.getComposite(
PorterDuffXfermode_Delegate.getPorterDuffMode(mode), 0xFF);
if (composite != null) {
graphics.setComposite(composite);
}
graphics.fillRect(0, 0, w, h);
}
graphics.fillRect(0, 0, canvasDelegate.mBufferedImage.getWidth(),
canvasDelegate.mBufferedImage.getHeight());
} finally {
// dispose Graphics2D object
graphics.dispose();
}
});
}
/*package*/ static void native_drawPaint(int nativeCanvas, int paint) {
@@ -547,32 +562,15 @@ public final class Canvas_Delegate {
throw new UnsupportedOperationException();
}
/*package*/ static void native_drawLine(int nativeCanvas, float startX,
float startY, float stopX,
float stopY, int paint) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
assert false;
return;
}
/*package*/ static void native_drawLine(int nativeCanvas,
final float startX, final float startY, final float stopX, final float stopY,
int paint) {
// get the delegate from the native int.
Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
if (paintDelegate == null) {
assert false;
return;
}
// get a Graphics2D object configured with the drawing parameters.
Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
try {
g.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY);
} finally {
// dispose Graphics2D object
g.dispose();
}
draw(nativeCanvas, paint, false /*compositeOnly*/, new GcSnapshot.Drawable() {
public void draw(Graphics2D graphics, Paint_Delegate paint) {
graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY);
}
});
}
/*package*/ static void native_drawRect(int nativeCanvas, RectF rect,
@@ -580,82 +578,47 @@ public final class Canvas_Delegate {
native_drawRect(nativeCanvas, rect.left, rect.top, rect.right, rect.bottom, paint);
}
/*package*/ static void native_drawRect(int nativeCanvas, float left,
float top, float right,
float bottom, int paint) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
assert false;
return;
}
/*package*/ static void native_drawRect(int nativeCanvas,
final float left, final float top, final float right, final float bottom, int paint) {
// get the delegate from the native int.
Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
if (paintDelegate == null) {
assert false;
return;
}
if (right > left && bottom > top) {
// get a Graphics2D object configured with the drawing parameters.
Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
try {
int style = paintDelegate.getStyle();
draw(nativeCanvas, paint, false /*compositeOnly*/, new GcSnapshot.Drawable() {
public void draw(Graphics2D graphics, Paint_Delegate paint) {
int style = paint.getStyle();
// draw
if (style == Paint.Style.FILL.nativeInt ||
style == Paint.Style.FILL_AND_STROKE.nativeInt) {
g.fillRect((int)left, (int)top, (int)(right-left), (int)(bottom-top));
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) {
g.drawRect((int)left, (int)top, (int)(right-left), (int)(bottom-top));
graphics.drawRect((int)left, (int)top, (int)(right-left), (int)(bottom-top));
}
} finally {
// dispose Graphics2D object
g.dispose();
}
}
});
}
/*package*/ static void native_drawOval(int nativeCanvas, RectF oval,
int paint) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
assert false;
return;
}
// get the delegate from the native int.
Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
if (paintDelegate == null) {
assert false;
return;
}
/*package*/ static void native_drawOval(int nativeCanvas, final RectF oval, int paint) {
if (oval.right > oval.left && oval.bottom > oval.top) {
// get a Graphics2D object configured with the drawing parameters.
Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
draw(nativeCanvas, paint, false /*compositeOnly*/, new GcSnapshot.Drawable() {
public void draw(Graphics2D graphics, Paint_Delegate paint) {
int style = paint.getStyle();
int style = paintDelegate.getStyle();
// draw
if (style == Paint.Style.FILL.nativeInt ||
style == Paint.Style.FILL_AND_STROKE.nativeInt) {
graphics.fillOval((int)oval.left, (int)oval.top,
(int)oval.width(), (int)oval.height());
}
// draw
if (style == Paint.Style.FILL.nativeInt ||
style == Paint.Style.FILL_AND_STROKE.nativeInt) {
g.fillOval((int)oval.left, (int)oval.top, (int)oval.width(), (int)oval.height());
}
if (style == Paint.Style.STROKE.nativeInt ||
style == Paint.Style.FILL_AND_STROKE.nativeInt) {
g.drawOval((int)oval.left, (int)oval.top, (int)oval.width(), (int)oval.height());
}
// dispose Graphics2D object
g.dispose();
if (style == Paint.Style.STROKE.nativeInt ||
style == Paint.Style.FILL_AND_STROKE.nativeInt) {
graphics.drawOval((int)oval.left, (int)oval.top,
(int)oval.width(), (int)oval.height());
}
}
});
}
}
@@ -675,46 +638,29 @@ public final class Canvas_Delegate {
}
/*package*/ static void native_drawRoundRect(int nativeCanvas,
RectF rect, float rx,
float ry, int paint) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
assert false;
return;
}
final RectF rect, final float rx, final float ry, int paint) {
// get the delegate from the native int.
Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
if (paintDelegate == null) {
assert false;
return;
}
draw(nativeCanvas, paint, false /*compositeOnly*/, new GcSnapshot.Drawable() {
if (rect.right > rect.left && rect.bottom > rect.top) {
// get a Graphics2D object configured with the drawing parameters.
Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
public void draw(Graphics2D graphics, Paint_Delegate paint) {
int style = paint.getStyle();
int style = paintDelegate.getStyle();
// draw
if (style == Paint.Style.FILL.nativeInt ||
style == Paint.Style.FILL_AND_STROKE.nativeInt) {
graphics.fillRoundRect(
(int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
(int)rx, (int)ry);
}
// draw
if (style == Paint.Style.FILL.nativeInt ||
style == Paint.Style.FILL_AND_STROKE.nativeInt) {
g.fillRoundRect(
(int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
(int)rx, (int)ry);
if (style == Paint.Style.STROKE.nativeInt ||
style == Paint.Style.FILL_AND_STROKE.nativeInt) {
graphics.drawRoundRect(
(int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
(int)rx, (int)ry);
}
}
if (style == Paint.Style.STROKE.nativeInt ||
style == Paint.Style.FILL_AND_STROKE.nativeInt) {
g.drawRoundRect(
(int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
(int)rx, (int)ry);
}
// dispose Graphics2D object
g.dispose();
}
});
}
/*package*/ static void native_drawPath(int nativeCanvas, int path,
@@ -725,7 +671,7 @@ public final class Canvas_Delegate {
return;
}
draw(nativeCanvas, paint, new Drawable() {
draw(nativeCanvas, paint, false /*compositeOnly*/, new GcSnapshot.Drawable() {
public void draw(Graphics2D graphics, Paint_Delegate paint) {
Shape shape = pathDelegate.getJavaShape();
int style = paint.getStyle();
@@ -846,116 +792,103 @@ public final class Canvas_Delegate {
throw new UnsupportedOperationException();
}
/*package*/ static void native_drawText(int nativeCanvas, char[] text,
int index, int count, float x,
float y, int flags, int paint) {
// WARNING: the logic in this method is similar to Paint.measureText.
// Any change to this method should be reflected in Paint.measureText
/*package*/ static void native_drawText(int nativeCanvas,
final char[] text, final int index, final int count,
final float startX, final float startY, int flags, int paint) {
draw(nativeCanvas, paint, false /*compositeOnly*/, new GcSnapshot.Drawable() {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
if (canvasDelegate == null) {
assert false;
return;
}
// get the delegate from the native int.
Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
if (paintDelegate == null) {
assert false;
return;
}
Graphics2D g = (Graphics2D) canvasDelegate.createCustomGraphics(paintDelegate);
try {
// Paint.TextAlign indicates how the text is positioned relative to X.
// LEFT is the default and there's nothing to do.
if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) {
float m = paintDelegate.measureText(text, index, count);
if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) {
x -= m / 2;
} else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
x -= m;
}
}
List<FontInfo> fonts = paintDelegate.getFonts();
if (fonts.size() > 0) {
FontInfo mainFont = fonts.get(0);
int i = index;
int lastIndex = index + count;
while (i < lastIndex) {
// always start with the main font.
int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex);
if (upTo == -1) {
// draw all the rest and exit.
g.setFont(mainFont.mFont);
g.drawChars(text, i, lastIndex - i, (int)x, (int)y);
return;
} else if (upTo > 0) {
// draw what's possible
g.setFont(mainFont.mFont);
g.drawChars(text, i, upTo - i, (int)x, (int)y);
// compute the width that was drawn to increase x
x += mainFont.mMetrics.charsWidth(text, i, upTo - i);
// move index to the first non displayed char.
i = upTo;
// don't call continue at this point. Since it is certain the main font
// cannot display the font a index upTo (now ==i), we move on to the
// fallback fonts directly.
public void draw(Graphics2D graphics, Paint_Delegate paint) {
// WARNING: the logic in this method is similar to Paint.measureText.
// Any change to this method should be reflected in Paint.measureText
// Paint.TextAlign indicates how the text is positioned relative to X.
// LEFT is the default and there's nothing to do.
float x = startX;
float y = startY;
if (paint.getTextAlign() != Paint.Align.LEFT.nativeInt) {
float m = paint.measureText(text, index, count);
if (paint.getTextAlign() == Paint.Align.CENTER.nativeInt) {
x -= m / 2;
} else if (paint.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
x -= m;
}
}
// no char supported, attempt to read the next char(s) with the
// fallback font. In this case we only test the first character
// and then go back to test with the main font.
// Special test for 2-char characters.
boolean foundFont = false;
for (int f = 1 ; f < fonts.size() ; f++) {
FontInfo fontInfo = fonts.get(f);
List<FontInfo> fonts = paint.getFonts();
// need to check that the font can display the character. We test
// differently if the char is a high surrogate.
int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount);
if (fonts.size() > 0) {
FontInfo mainFont = fonts.get(0);
int i = index;
int lastIndex = index + count;
while (i < lastIndex) {
// always start with the main font.
int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex);
if (upTo == -1) {
// draw that char
g.setFont(fontInfo.mFont);
g.drawChars(text, i, charCount, (int)x, (int)y);
// draw all the rest and exit.
graphics.setFont(mainFont.mFont);
graphics.drawChars(text, i, lastIndex - i, (int)x, (int)y);
return;
} else if (upTo > 0) {
// draw what's possible
graphics.setFont(mainFont.mFont);
graphics.drawChars(text, i, upTo - i, (int)x, (int)y);
// update x
x += fontInfo.mMetrics.charsWidth(text, i, charCount);
// compute the width that was drawn to increase x
x += mainFont.mMetrics.charsWidth(text, i, upTo - i);
// update the index in the text, and move on
// move index to the first non displayed char.
i = upTo;
// don't call continue at this point. Since it is certain the main font
// cannot display the font a index upTo (now ==i), we move on to the
// fallback fonts directly.
}
// no char supported, attempt to read the next char(s) with the
// fallback font. In this case we only test the first character
// and then go back to test with the main font.
// Special test for 2-char characters.
boolean foundFont = false;
for (int f = 1 ; f < fonts.size() ; f++) {
FontInfo fontInfo = fonts.get(f);
// need to check that the font can display the character. We test
// differently if the char is a high surrogate.
int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount);
if (upTo == -1) {
// draw that char
graphics.setFont(fontInfo.mFont);
graphics.drawChars(text, i, charCount, (int)x, (int)y);
// update x
x += fontInfo.mMetrics.charsWidth(text, i, charCount);
// update the index in the text, and move on
i += charCount;
foundFont = true;
break;
}
}
// in case no font can display the char, display it with the main font.
// (it'll put a square probably)
if (foundFont == false) {
int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
graphics.setFont(mainFont.mFont);
graphics.drawChars(text, i, charCount, (int)x, (int)y);
// measure it to advance x
x += mainFont.mMetrics.charsWidth(text, i, charCount);
// and move to the next chars.
i += charCount;
foundFont = true;
break;
}
}
// in case no font can display the char, display it with the main font.
// (it'll put a square probably)
if (foundFont == false) {
int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
g.setFont(mainFont.mFont);
g.drawChars(text, i, charCount, (int)x, (int)y);
// measure it to advance x
x += mainFont.mMetrics.charsWidth(text, i, charCount);
// and move to the next chars.
i += charCount;
}
}
}
} finally {
g.dispose();
}
});
}
/*package*/ static void native_drawText(int nativeCanvas, String text,
@@ -1042,8 +975,11 @@ public final class Canvas_Delegate {
/**
* Executes a {@link 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, int, int)}.
*/
private static void draw(int nCanvas, int nPaint, Drawable drawable) {
private static void draw(int nCanvas, int nPaint, boolean compositeOnly,
GcSnapshot.Drawable drawable) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
if (canvasDelegate == null) {
@@ -1051,28 +987,42 @@ public final class Canvas_Delegate {
return;
}
Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
if (paintDelegate == null) {
// paint could be 0 meaning no paint
Paint_Delegate paintDelegate = null;
if (nPaint > 0) {
paintDelegate = Paint_Delegate.getDelegate(nPaint);
if (paintDelegate == null) {
assert false;
return;
}
}
canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly);
}
/**
* Executes a {@link 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, int, int)}.
*/
private static void draw(int nCanvas, GcSnapshot.Drawable drawable) {
// get the delegate from the native int.
Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
if (canvasDelegate == null) {
assert false;
return;
}
// get a Graphics2D object configured with the drawing parameters.
Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
try {
drawable.draw(g, paintDelegate);
} finally {
// dispose Graphics2D object
g.dispose();
}
canvasDelegate.mSnapshot.draw(drawable);
}
private Canvas_Delegate(BufferedImage image) {
setBitmap(image);
mSnapshot = GcSnapshot.createDefaultSnapshot(mBufferedImage = image);
}
private Canvas_Delegate() {
mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/);
}
/**
@@ -1086,8 +1036,23 @@ public final class Canvas_Delegate {
// get the current save count
int count = mSnapshot.size();
// create a new snapshot and add it to the stack
mSnapshot = new GcSnapshot(mSnapshot, saveFlags);
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;
@@ -1116,126 +1081,16 @@ public final class Canvas_Delegate {
private void setBitmap(BufferedImage image) {
mBufferedImage = image;
assert mSnapshot.size() == 1;
mSnapshot.setGraphics2D(mBufferedImage.createGraphics());
}
/**
* Creates a new {@link Graphics2D} based on the {@link Paint} parameters.
* <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used.
*/
/*package*/ Graphics2D createCustomGraphics(Paint_Delegate paint) {
// make new one
Graphics2D g = getGcSnapshot().create();
// configure it
if (paint.isAntiAliased()) {
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
}
// get the shader first, as it'll replace the color if it can be used it.
boolean customShader = false;
int nativeShader = paint.getShader();
if (nativeShader > 0) {
Shader_Delegate shaderDelegate = Shader_Delegate.getDelegate(nativeShader);
assert shaderDelegate != null;
if (shaderDelegate != null) {
if (shaderDelegate.isSupported()) {
java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint();
assert shaderPaint != null;
if (shaderPaint != null) {
g.setPaint(shaderPaint);
customShader = true;
}
} else {
Bridge.getLog().fidelityWarning(null,
shaderDelegate.getSupportMessage(),
null);
}
}
}
// if no shader, use the paint color
if (customShader == false) {
g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));
}
boolean customStroke = false;
int pathEffect = paint.getPathEffect();
if (pathEffect > 0) {
PathEffect_Delegate effectDelegate = PathEffect_Delegate.getDelegate(pathEffect);
assert effectDelegate != null;
if (effectDelegate != null) {
if (effectDelegate.isSupported()) {
Stroke stroke = effectDelegate.getStroke(paint);
assert stroke != null;
if (stroke != null) {
g.setStroke(stroke);
customStroke = true;
}
} else {
Bridge.getLog().fidelityWarning(null,
effectDelegate.getSupportMessage(),
null);
}
}
}
// if no custom stroke as been set, set the default one.
if (customStroke == false) {
g.setStroke(new BasicStroke(
paint.getStrokeWidth(),
paint.getJavaCap(),
paint.getJavaJoin(),
paint.getJavaStrokeMiter()));
}
// the alpha for the composite. Always opaque if the normal paint color is used since
// it contains the alpha
int alpha = customShader ? paint.getAlpha() : 0xFF;
boolean customXfermode = false;
int xfermode = paint.getXfermode();
if (xfermode > 0) {
Xfermode_Delegate xfermodeDelegate = Xfermode_Delegate.getDelegate(paint.getXfermode());
assert xfermodeDelegate != null;
if (xfermodeDelegate != null) {
if (xfermodeDelegate.isSupported()) {
Composite composite = xfermodeDelegate.getComposite(alpha);
assert composite != null;
if (composite != null) {
g.setComposite(composite);
customXfermode = true;
}
} else {
Bridge.getLog().fidelityWarning(null,
xfermodeDelegate.getSupportMessage(),
null);
}
}
}
// if there was no custom xfermode, but we have alpha (due to a shader and a non
// opaque alpha channel in the paint color), then we create an AlphaComposite anyway
// that will handle the alpha.
if (customXfermode == false && alpha != 0xFF) {
g.setComposite(PorterDuffXfermode_Delegate.getComposite(
PorterDuff.Mode.SRC_OVER, alpha));
}
return g;
mSnapshot.setImage(mBufferedImage);
}
private static void drawBitmap(
int nativeCanvas,
BufferedImage image,
Bitmap.Config mBitmapConfig,
final BufferedImage image,
final Bitmap.Config mBitmapConfig,
int nativePaintOrZero,
int sleft, int stop, int sright, int sbottom,
int dleft, int dtop, int dright, int dbottom) {
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) {
@@ -1253,47 +1108,35 @@ public final class Canvas_Delegate {
}
}
drawBitmap(canvasDelegate, image, mBitmapConfig, paintDelegate,
sleft, stop, sright, sbottom,
dleft, dtop, dright, dbottom);
}
// if the bitmap config is alpha_8, then we erase all color value from it
// before drawing it.
if (mBitmapConfig == Bitmap.Config.ALPHA_8) {
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());
private static void drawBitmap(
Canvas_Delegate canvasDelegate,
BufferedImage image,
Bitmap.Config mBitmapConfig,
Paint_Delegate paintDelegate,
int sleft, int stop, int sright, int sbottom,
int dleft, int dtop, int dright, int dbottom) {
//FIXME add support for canvas, screen and bitmap densities.
Graphics2D g = canvasDelegate.getGcSnapshot().create();
try {
if (paintDelegate != null && paintDelegate.isFilterBitmap()) {
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
final int length = argb.length;
for (int i = 0 ; i < length; i++) {
argb[i] &= 0xFF000000;
}
// if the bitmap config is alpha_8, then we erase all color value from it
// before drawing it.
if (mBitmapConfig == Bitmap.Config.ALPHA_8) {
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);
}
g.drawImage(image, dleft, dtop, dright, dbottom,
sleft, stop, sright, sbottom, null);
} finally {
g.dispose();
image.setRGB(0, 0, w, h, argb, 0, w);
}
draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/,
new GcSnapshot.Drawable() {
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);
}
});
}
}

View File

@@ -55,7 +55,7 @@ public class ComposePathEffect_Delegate extends PathEffect_Delegate {
@Override
public String getSupportMessage() {
return "Composte Path Effects are not supported in Layout Preview mode.";
return "Compose Path Effects are not supported in Layout Preview mode.";
}
// ---- native methods ----

View File

@@ -55,7 +55,7 @@ public class ComposeShader_Delegate extends Shader_Delegate {
@Override
public String getSupportMessage() {
return "Compose Shader are not supported in Layout Preview mode.";
return "Compose Shaders are not supported in Layout Preview mode.";
}

View File

@@ -106,6 +106,45 @@ public final class Matrix_Delegate {
return true;
}
public static Matrix_Delegate make(AffineTransform matrix) {
float[] values = new float[MATRIX_SIZE];
values[0] = (float) matrix.getScaleX();
values[1] = (float) matrix.getShearX();
values[2] = (float) matrix.getTranslateX();
values[3] = (float) matrix.getShearY();
values[4] = (float) matrix.getScaleY();
values[5] = (float) matrix.getTranslateY();
values[6] = 0.f;
values[7] = 0.f;
values[8] = 1.f;
return new Matrix_Delegate(values);
}
public boolean mapRect(RectF dst, RectF src) {
// array with 4 corners
float[] corners = new float[] {
src.left, src.top,
src.right, src.top,
src.right, src.bottom,
src.left, src.bottom,
};
// apply the transform to them.
mapPoints(corners);
// now put the result in the rect. We take the min/max of Xs and min/max of Ys
dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
return (computeTypeMask() & kRectStaysRect_Mask) != 0;
}
/**
* Returns an {@link AffineTransform} matching the matrix.
*/
@@ -655,26 +694,7 @@ public final class Matrix_Delegate {
return false;
}
// array with 4 corners
float[] corners = new float[] {
src.left, src.top,
src.right, src.top,
src.right, src.bottom,
src.left, src.bottom,
};
// apply the transform to them.
d.mapPoints(corners);
// now put the result in the rect. We take the min/max of Xs and min/max of Ys
dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
return (d.computeTypeMask() & kRectStaysRect_Mask) != 0;
return d.mapRect(dst, src);
}
/*package*/ static float native_mapRadius(int native_object, float radius) {
@@ -740,7 +760,6 @@ public final class Matrix_Delegate {
matrix[4], matrix[2], matrix[5]);
}
/**
* Reset a matrix to the identity
*/
@@ -830,6 +849,10 @@ public final class Matrix_Delegate {
reset();
}
private Matrix_Delegate(float[] values) {
System.arraycopy(values, 0, mValues, 0, MATRIX_SIZE);
}
/**
* Adds the given transformation to the current Matrix
* <p/>This in effect does this = this*matrix

View File

@@ -18,6 +18,7 @@ package android.graphics;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.layoutlib.bridge.impl.GcSnapshot;
import com.android.ninepatch.NinePatchChunk;
import android.graphics.drawable.NinePatchDrawable;
@@ -123,11 +124,11 @@ public final class NinePatch_Delegate {
}
private static void draw(int canvas_instance,
int left, int top, int right, int bottom,
final int left, final int top, final int right, final int bottom,
int bitmap_instance, byte[] c, int paint_instance_or_null,
int destDensity, int srcDensity) {
final int destDensity, final int srcDensity) {
// get the delegate from the native int.
Bitmap_Delegate bitmap_delegate = Bitmap_Delegate.getDelegate(bitmap_instance);
final Bitmap_Delegate bitmap_delegate = Bitmap_Delegate.getDelegate(bitmap_instance);
if (bitmap_delegate == null) {
assert false;
return;
@@ -143,7 +144,7 @@ public final class NinePatch_Delegate {
return;
}
NinePatchChunk chunkObject = getChunk(c);
final NinePatchChunk chunkObject = getChunk(c);
assert chunkObject != null;
if (chunkObject == null) {
return;
@@ -158,19 +159,13 @@ public final class NinePatch_Delegate {
// this one can be null
Paint_Delegate paint_delegate = Paint_Delegate.getDelegate(paint_instance_or_null);
Graphics2D graphics;
if (paint_delegate != null) {
graphics = canvas_delegate.createCustomGraphics(paint_delegate);
} else {
graphics = canvas_delegate.getGcSnapshot().create();
}
canvas_delegate.getSnapshot().draw(new GcSnapshot.Drawable() {
public void draw(Graphics2D graphics, Paint_Delegate paint) {
chunkObject.draw(bitmap_delegate.getImage(), graphics,
left, top, right - left, bottom - top, destDensity, srcDensity);
}
}, paint_delegate, true /*compositeOnly*/);
try {
chunkObject.draw(bitmap_delegate.getImage(), graphics,
left, top, right - left, bottom - top, destDensity, srcDensity);
} finally {
graphics.dispose();
}
}
/*package*/ static int nativeGetTransparentRegion(int bitmap, byte[] chunk, Rect location) {

View File

@@ -118,6 +118,10 @@ public class Paint_Delegate {
return mColor >>> 24;
}
public void setAlpha(int alpha) {
mColor = (alpha << 24) | (mColor & 0x00FFFFFF);
}
public int getTextAlign() {
return mTextAlign;
}
@@ -130,7 +134,11 @@ public class Paint_Delegate {
* returns the value of stroke miter needed by the java api.
*/
public float getJavaStrokeMiter() {
return mStrokeMiter * mStrokeWidth;
float miter = mStrokeMiter * mStrokeWidth;
if (miter < 1.f) {
miter = 1.f;
}
return miter;
}
public int getJavaCap() {
@@ -274,7 +282,7 @@ public class Paint_Delegate {
return;
}
delegate.mColor = (a << 24) | (delegate.mColor & 0x00FFFFFF);
delegate.setAlpha(a);
}
/*package*/ static float getStrokeWidth(Paint thisPaint) {
@@ -835,7 +843,7 @@ public class Paint_Delegate {
// ---- Private delegate/helper methods ----
private Paint_Delegate() {
/*package*/ Paint_Delegate() {
reset();
}
@@ -866,14 +874,14 @@ public class Paint_Delegate {
private void reset() {
mFlags = Paint.DEFAULT_PAINT_FLAGS;
mColor = 0;
mColor = 0xFF000000;
mStyle = Paint.Style.FILL.nativeInt;
mCap = Paint.Cap.BUTT.nativeInt;
mJoin = Paint.Join.MITER.nativeInt;
mTextAlign = 0;
mTypeface = Typeface.sDefaults[0].native_instance;
mStrokeWidth = 1.f;
mStrokeMiter = 2.f;
mStrokeMiter = 4.f;
mTextSize = 20.f;
mTextScaleX = 1.f;
mTextSkewX = 0.f;

View File

@@ -107,7 +107,8 @@ public class BridgeRenderSession extends RenderSession {
Bridge.prepareThread();
mLastResult = mSession.acquire(Params.DEFAULT_TIMEOUT);
if (mLastResult.isSuccess()) {
mLastResult = mSession.insertChild((ViewGroup) parentView, childXml, index, listener);
mLastResult = mSession.insertChild((ViewGroup) parentView, childXml, index,
listener);
}
} finally {
mSession.release();

View File

@@ -16,20 +16,42 @@
package com.android.layoutlib.bridge.impl;
import android.graphics.Canvas;
import android.graphics.Region;
import com.android.layoutlib.bridge.Bridge;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint_Delegate;
import android.graphics.PathEffect_Delegate;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode_Delegate;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Shader_Delegate;
import android.graphics.Xfermode_Delegate;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
/**
* Class representing a graphics context snapshot, as well as a context stack as a linked list.
* <p>
* This is based on top of {@link Graphics2D} but can operate independently if none are available
* yet when setting transforms and clip information.
* <p>
* This allows for drawing through {@link #draw(Drawable, Paint_Delegate)} and
* {@link #draw(Drawable, Paint_Delegate)}
*
*/
public class GcSnapshot {
@@ -37,38 +59,209 @@ public class GcSnapshot {
private final GcSnapshot mPrevious;
private final int mFlags;
private Graphics2D mGraphics2D = null;
/** list of layers. */
private final ArrayList<Layer> mLayers = new ArrayList<Layer>();
/** temp transform in case transformation are set before a Graphics2D exists */
private AffineTransform mTransform = null;
/** temp clip in case clipping is set before a Graphics2D exists */
private Area mClip = null;
// local layer data
private final Layer mLocalLayer;
private final Paint_Delegate mLocalLayerPaint;
private Rect mLocalLayerRegion;
public interface Drawable {
void draw(Graphics2D graphics, Paint_Delegate paint);
}
/**
* Creates a new {@link GcSnapshot} on top of another one.
* <p/>
* This is basically the equivalent of {@link Canvas#save(int)}
* @param previous the previous snapshot head.
* @param flags the flags regarding what should be saved.
* class containing information about a layer.
*/
public GcSnapshot(GcSnapshot previous, int flags) {
assert previous != null;
mPrevious = previous;
mFlags = flags;
mGraphics2D = (Graphics2D) previous.mGraphics2D.create();
private static class Layer {
private final Graphics2D mGraphics;
private final BufferedImage mImage;
private BufferedImage mOriginalCopy;
Layer(Graphics2D graphics, BufferedImage image) {
mGraphics = graphics;
mImage = image;
}
/** The Graphics2D, guaranteed to be non null */
Graphics2D getGraphics() {
return mGraphics;
}
/** The BufferedImage, guaranteed to be non null */
BufferedImage getImage() {
return mImage;
}
Layer makeCopy() {
return new Layer((Graphics2D) mGraphics.create(), mImage);
}
/** sets an optional copy of the original content to be used during restore */
void setOriginalCopy(BufferedImage image) {
mOriginalCopy = image;
}
BufferedImage getOriginalCopy() {
return mOriginalCopy;
}
}
/**
* Creates the root snapshot associating it with a given image.
* <p>
* If <var>image</var> is null, then {@link GcSnapshot#setImage(BufferedImage)} must be
* called before the snapshot can be used to draw. Transform and clip operations are permitted
* before.
*
* @param image the image to associate to the snapshot or null.
* @return the root snapshot
*/
public static GcSnapshot createDefaultSnapshot(BufferedImage image) {
GcSnapshot snapshot = new GcSnapshot();
if (image != null) {
snapshot.setImage(image);
}
return snapshot;
}
/**
* Saves the current state according to the given flags and returns the new current snapshot.
* <p/>
* This is the equivalent of {@link Canvas#save(int)}
*
* @param flags the save flags.
* @return the new snapshot
*
* @see Canvas#save(int)
*/
public GcSnapshot save(int flags) {
return new GcSnapshot(this, null /*layerbounds*/, null /*paint*/, flags);
}
/**
* Saves the current state and creates a new layer, and returns the new current snapshot.
* <p/>
* This is the equivalent of {@link Canvas#saveLayer(RectF, Paint, int)}
*
* @param layerBounds the layer bounds
* @param paint the Paint information used to blit the layer back into the layers underneath
* upon restore
* @param flags the save flags.
* @return the new snapshot
*
* @see Canvas#saveLayer(RectF, Paint, int)
*/
public GcSnapshot saveLayer(RectF layerBounds, Paint_Delegate paint, int flags) {
return new GcSnapshot(this, layerBounds, paint, flags);
}
/**
* Creates the root snapshot.
* {@link #setGraphics2D(Graphics2D)} will have to be called on it when possible.
*/
public GcSnapshot() {
private GcSnapshot() {
mPrevious = null;
mFlags = 0;
mLocalLayer = null;
mLocalLayerPaint = null;
}
/**
* Creates a new {@link GcSnapshot} on top of another one, with a layer data to be restored
* into the main graphics when {@link #restore()} is called.
*
* @param previous the previous snapshot head.
* @param layerBounds the region of the layer. Optional, if null, this is a normal save()
* @param paint the Paint information used to blit the layer back into the layers underneath
* upon restore
* @param flags the flags regarding what should be saved.
*/
private GcSnapshot(GcSnapshot previous, RectF layerBounds, Paint_Delegate paint, int flags) {
assert previous != null;
mPrevious = previous;
mFlags = flags;
// make a copy of the current layers before adding the new one.
// This keeps the same BufferedImage reference but creates new Graphics2D for this
// snapshot.
// It does not copy whatever original copy the layers have, as they will be done
// only if the new layer doesn't clip drawing to itself.
for (Layer layer : mPrevious.mLayers) {
mLayers.add(layer.makeCopy());
}
if (layerBounds != null) {
// get the current transform
AffineTransform matrix = mLayers.get(0).getGraphics().getTransform();
// transform the layerBounds and puts it into a int rect
RectF rect2 = new RectF();
mapRect(matrix, rect2, layerBounds);
mLocalLayerRegion = new Rect();
rect2.round(mLocalLayerRegion);
// get the base layer (always at index 0)
Layer baseLayer = mLayers.get(0);
// create the image for the layer
BufferedImage layerImage = new BufferedImage(
baseLayer.getImage().getWidth(),
baseLayer.getImage().getHeight(),
BufferedImage.TYPE_INT_ARGB);
// create a graphics for it so that drawing can be done.
Graphics2D layerGraphics = layerImage.createGraphics();
// because this layer inherits the current context for transform and clip,
// set them to one from the base layer.
AffineTransform currentMtx = baseLayer.getGraphics().getTransform();
layerGraphics.setTransform(currentMtx);
Shape currentClip = baseLayer.getGraphics().getClip();
layerGraphics.setClip(currentClip);
// create a new layer for this new layer and add it to the list at the end.
mLayers.add(mLocalLayer = new Layer(layerGraphics, layerImage));
// if the drawing is not clipped to the local layer only, we save the current content
// of all other layers. We are only interested in the part that will actually
// be drawn, so we create as small bitmaps as we can.
// This is so that we can erase the drawing that goes in the layers below that will
// be coming from the layer itself.
if ((mFlags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0) {
int w = mLocalLayerRegion.width();
int h = mLocalLayerRegion.height();
for (int i = 0 ; i < mLayers.size() - 1 ; i++) {
Layer layer = mLayers.get(i);
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = image.createGraphics();
graphics.drawImage(layer.getImage(),
0, 0, w, h,
mLocalLayerRegion.left, mLocalLayerRegion.top,
mLocalLayerRegion.right, mLocalLayerRegion.bottom,
null);
graphics.dispose();
layer.setOriginalCopy(image);
}
}
} else {
mLocalLayer = null;
}
mLocalLayerPaint = paint;
}
public void dispose() {
if (mGraphics2D != null) {
mGraphics2D.dispose();
for (Layer layer : mLayers) {
layer.getGraphics().dispose();
}
if (mPrevious != null) {
@@ -102,35 +295,35 @@ public class GcSnapshot {
}
/**
* Sets the Graphics2D object for this snapshot if it was created through {@link #GcSnapshot()}.
* Link the snapshot to a BufferedImage.
* <p/>
* This is only for the case where the snapshot was created with a null image when calling
* {@link #createDefaultSnapshot(BufferedImage)}, and is therefore not yet linked to
* a previous snapshot.
* <p/>
* If any transform or clip information was set before, they are put into the Graphics object.
* @param graphics2D the graphics object to set.
* @param image the buffered image to link to.
*/
public void setGraphics2D(Graphics2D graphics2D) {
mGraphics2D = graphics2D;
public void setImage(BufferedImage image) {
assert mLayers.size() == 0;
Graphics2D graphics2D = image.createGraphics();
mLayers.add(new Layer(graphics2D, image));
if (mTransform != null) {
mGraphics2D.setTransform(mTransform);
graphics2D.setTransform(mTransform);
mTransform = null;
}
if (mClip != null) {
mGraphics2D.setClip(mClip);
graphics2D.setClip(mClip);
mClip = null;
}
}
/**
* Creates and return a copy of the current {@link Graphics2D}.
* @return a new {@link Graphics2D}.
*/
public Graphics2D create() {
assert mGraphics2D != null;
return (Graphics2D) mGraphics2D.create();
}
public void translate(float dx, float dy) {
if (mGraphics2D != null) {
mGraphics2D.translate(dx, dy);
if (mLayers.size() > 0) {
for (Layer layer : mLayers) {
layer.getGraphics().translate(dx, dy);
}
} else {
if (mTransform == null) {
mTransform = new AffineTransform();
@@ -140,8 +333,10 @@ public class GcSnapshot {
}
public void rotate(double radians) {
if (mGraphics2D != null) {
mGraphics2D.rotate(radians);
if (mLayers.size() > 0) {
for (Layer layer : mLayers) {
layer.getGraphics().rotate(radians);
}
} else {
if (mTransform == null) {
mTransform = new AffineTransform();
@@ -151,8 +346,10 @@ public class GcSnapshot {
}
public void scale(float sx, float sy) {
if (mGraphics2D != null) {
mGraphics2D.scale(sx, sy);
if (mLayers.size() > 0) {
for (Layer layer : mLayers) {
layer.getGraphics().scale(sx, sy);
}
} else {
if (mTransform == null) {
mTransform = new AffineTransform();
@@ -162,8 +359,9 @@ public class GcSnapshot {
}
public AffineTransform getTransform() {
if (mGraphics2D != null) {
return mGraphics2D.getTransform();
if (mLayers.size() > 0) {
// all graphics2D in the list have the same transform
return mLayers.get(0).getGraphics().getTransform();
} else {
if (mTransform == null) {
mTransform = new AffineTransform();
@@ -173,8 +371,10 @@ public class GcSnapshot {
}
public void setTransform(AffineTransform transform) {
if (mGraphics2D != null) {
mGraphics2D.setTransform(transform);
if (mLayers.size() > 0) {
for (Layer layer : mLayers) {
layer.getGraphics().setTransform(transform);
}
} else {
if (mTransform == null) {
mTransform = new AffineTransform();
@@ -184,40 +384,52 @@ public class GcSnapshot {
}
public boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
if (mGraphics2D != null) {
if (mLayers.size() > 0) {
Shape clip = null;
if (regionOp == Region.Op.DIFFERENCE.nativeInt) {
Area newClip = new Area(mGraphics2D.getClip());
Area newClip = new Area(getClip());
newClip.subtract(new Area(
new Rectangle2D.Float(left, top, right - left, bottom - top)));
mGraphics2D.setClip(newClip);
clip = newClip;
} else if (regionOp == Region.Op.INTERSECT.nativeInt) {
mGraphics2D.clipRect((int) left, (int) top,
(int) (right - left), (int) (bottom - top));
for (Layer layer : mLayers) {
layer.getGraphics().clipRect(
(int) left, (int) top, (int) (right - left), (int) (bottom - top));
}
} else if (regionOp == Region.Op.UNION.nativeInt) {
Area newClip = new Area(mGraphics2D.getClip());
Area newClip = new Area(getClip());
newClip.add(new Area(
new Rectangle2D.Float(left, top, right - left, bottom - top)));
mGraphics2D.setClip(newClip);
clip = newClip;
} else if (regionOp == Region.Op.XOR.nativeInt) {
Area newClip = new Area(mGraphics2D.getClip());
Area newClip = new Area(getClip());
newClip.exclusiveOr(new Area(
new Rectangle2D.Float(left, top, right - left, bottom - top)));
mGraphics2D.setClip(newClip);
clip = newClip;
} else if (regionOp == Region.Op.REVERSE_DIFFERENCE.nativeInt) {
Area newClip = new Area(
new Rectangle2D.Float(left, top, right - left, bottom - top));
newClip.subtract(new Area(mGraphics2D.getClip()));
mGraphics2D.setClip(newClip);
newClip.subtract(new Area(getClip()));
clip = newClip;
} else if (regionOp == Region.Op.REPLACE.nativeInt) {
mGraphics2D.setClip((int) left, (int) top,
(int) (right - left), (int) (bottom - top));
for (Layer layer : mLayers) {
layer.getGraphics().setClip(
(int) left, (int) top, (int) (right - left), (int) (bottom - top));
}
}
return mGraphics2D.getClip().getBounds().isEmpty() == false;
if (clip != null) {
for (Layer layer : mLayers) {
layer.getGraphics().setClip(clip);
}
}
return getClip().getBounds().isEmpty() == false;
} else {
if (mClip == null) {
mClip = new Area();
@@ -238,8 +450,9 @@ public class GcSnapshot {
}
public Shape getClip() {
if (mGraphics2D != null) {
return mGraphics2D.getClip();
if (mLayers.size() > 0) {
// they all have the same clip
return mLayers.get(0).getGraphics().getClip();
} else {
if (mClip == null) {
mClip = new Area();
@@ -263,26 +476,261 @@ public class GcSnapshot {
}
}
/**
* Executes the Drawable's draw method, with a null paint delegate.
* <p/>
* Note that the method can be called several times if there are more than one active layer.
* @param drawable
*/
public void draw(Drawable drawable) {
draw(drawable, null, false /*compositeOnly*/);
}
/**
* Executes the Drawable's draw method.
* <p/>
* Note that the method can be called several times if there are more than one active layer.
* @param drawable
* @param paint
* @param compositeOnly whether the paint is used for composite only. This is typically
* the case for bitmaps.
*/
public void draw(Drawable drawable, Paint_Delegate paint, boolean compositeOnly) {
// if we clip to the layer, then we only draw in the layer
if (mLocalLayer != null && (mFlags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) != 0) {
drawInLayer(mLocalLayer, drawable, paint, compositeOnly);
} else {
// draw in all the layers
for (Layer layer : mLayers) {
drawInLayer(layer, drawable, paint, compositeOnly);
}
}
}
private void drawInLayer(Layer layer, Drawable drawable, Paint_Delegate paint,
boolean compositeOnly) {
Graphics2D originalGraphics = layer.getGraphics();
// get a Graphics2D object configured with the drawing parameters.
Graphics2D configuredGraphics2D =
paint != null ?
createCustomGraphics(originalGraphics, paint, compositeOnly) :
(Graphics2D) originalGraphics.create();
try {
drawable.draw(configuredGraphics2D, paint);
} finally {
// dispose Graphics2D object
configuredGraphics2D.dispose();
}
}
private GcSnapshot doRestore() {
// if this snapshot does not save everything, then set the previous snapshot
// to this snapshot content
if (mPrevious != null) {
boolean forceAllSave = false;
if (mLocalLayer != null) {
forceAllSave = true; // layers always save both clip and transform
// prepare to draw the current layer in the previous layers, ie all layers but
// the last one, since the last one is the local layer
for (int i = 0 ; i < mLayers.size() - 1 ; i++) {
Layer layer = mLayers.get(i);
Graphics2D baseGfx = layer.getImage().createGraphics();
// if the layer contains an original copy this means the flags
// didn't restrict drawing to the local layer and we need to make sure the
// layer bounds in the layer beneath didn't receive any drawing.
// so we use the originalCopy to erase the new drawings in there.
BufferedImage originalCopy = layer.getOriginalCopy();
if (originalCopy != null) {
Graphics2D g = (Graphics2D) baseGfx.create();
g.setComposite(AlphaComposite.Src);
g.drawImage(originalCopy,
mLocalLayerRegion.left, mLocalLayerRegion.top,
mLocalLayerRegion.right, mLocalLayerRegion.bottom,
0, 0, mLocalLayerRegion.width(), mLocalLayerRegion.height(),
null);
g.dispose();
}
// now draw put the content of the local layer onto the layer, using the paint
// information
Graphics2D g = createCustomGraphics(baseGfx, mLocalLayerPaint,
true /*alphaOnly*/);
g.drawImage(mLocalLayer.getImage(),
mLocalLayerRegion.left, mLocalLayerRegion.top,
mLocalLayerRegion.right, mLocalLayerRegion.bottom,
mLocalLayerRegion.left, mLocalLayerRegion.top,
mLocalLayerRegion.right, mLocalLayerRegion.bottom,
null);
g.dispose();
baseGfx.dispose();
}
}
// if this snapshot does not save everything, then set the previous snapshot
// to this snapshot content
// didn't save the matrix? set the current matrix on the previous snapshot
if ((mFlags & Canvas.MATRIX_SAVE_FLAG) == 0) {
mPrevious.mGraphics2D.setTransform(getTransform());
if (forceAllSave == false && (mFlags & Canvas.MATRIX_SAVE_FLAG) == 0) {
AffineTransform mtx = getTransform();
for (Layer layer : mPrevious.mLayers) {
layer.getGraphics().setTransform(mtx);
}
}
// didn't save the clip? set the current clip on the previous snapshot
if ((mFlags & Canvas.CLIP_SAVE_FLAG) == 0) {
mPrevious.mGraphics2D.setClip(mGraphics2D.getClip());
if (forceAllSave == false && (mFlags & Canvas.CLIP_SAVE_FLAG) == 0) {
Shape clip = getClip();
for (Layer layer : mPrevious.mLayers) {
layer.getGraphics().setClip(clip);
}
}
}
if (mGraphics2D != null) {
mGraphics2D.dispose();
for (Layer layer : mLayers) {
layer.getGraphics().dispose();
}
return mPrevious;
}
/**
* Creates a new {@link Graphics2D} based on the {@link Paint} parameters.
* <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used.
*/
private Graphics2D createCustomGraphics(Graphics2D original, Paint_Delegate paint,
boolean compositeOnly) {
// make new one graphics
Graphics2D g = (Graphics2D) original.create();
// configure it
if (paint.isAntiAliased()) {
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
}
boolean customShader = false;
// get the shader first, as it'll replace the color if it can be used it.
if (compositeOnly == false) {
int nativeShader = paint.getShader();
if (nativeShader > 0) {
Shader_Delegate shaderDelegate = Shader_Delegate.getDelegate(nativeShader);
assert shaderDelegate != null;
if (shaderDelegate != null) {
if (shaderDelegate.isSupported()) {
java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint();
assert shaderPaint != null;
if (shaderPaint != null) {
g.setPaint(shaderPaint);
customShader = true;
}
} else {
Bridge.getLog().fidelityWarning(null,
shaderDelegate.getSupportMessage(),
null);
}
}
}
// if no shader, use the paint color
if (customShader == false) {
g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));
}
boolean customStroke = false;
int pathEffect = paint.getPathEffect();
if (pathEffect > 0) {
PathEffect_Delegate effectDelegate = PathEffect_Delegate.getDelegate(pathEffect);
assert effectDelegate != null;
if (effectDelegate != null) {
if (effectDelegate.isSupported()) {
Stroke stroke = effectDelegate.getStroke(paint);
assert stroke != null;
if (stroke != null) {
g.setStroke(stroke);
customStroke = true;
}
} else {
Bridge.getLog().fidelityWarning(null,
effectDelegate.getSupportMessage(),
null);
}
}
}
// if no custom stroke as been set, set the default one.
if (customStroke == false) {
g.setStroke(new BasicStroke(
paint.getStrokeWidth(),
paint.getJavaCap(),
paint.getJavaJoin(),
paint.getJavaStrokeMiter()));
}
}
// the alpha for the composite. Always opaque if the normal paint color is used since
// it contains the alpha
int alpha = (compositeOnly || customShader) ? paint.getAlpha() : 0xFF;
boolean customXfermode = false;
int xfermode = paint.getXfermode();
if (xfermode > 0) {
Xfermode_Delegate xfermodeDelegate = Xfermode_Delegate.getDelegate(paint.getXfermode());
assert xfermodeDelegate != null;
if (xfermodeDelegate != null) {
if (xfermodeDelegate.isSupported()) {
Composite composite = xfermodeDelegate.getComposite(alpha);
assert composite != null;
if (composite != null) {
g.setComposite(composite);
customXfermode = true;
}
} else {
Bridge.getLog().fidelityWarning(null,
xfermodeDelegate.getSupportMessage(),
null);
}
}
}
// if there was no custom xfermode, but we have alpha (due to a shader and a non
// opaque alpha channel in the paint color), then we create an AlphaComposite anyway
// that will handle the alpha.
if (customXfermode == false && alpha != 0xFF) {
g.setComposite(PorterDuffXfermode_Delegate.getComposite(
PorterDuff.Mode.SRC_OVER, alpha));
}
return g;
}
private void mapRect(AffineTransform matrix, RectF dst, RectF src) {
// array with 4 corners
float[] corners = new float[] {
src.left, src.top,
src.right, src.top,
src.right, src.bottom,
src.left, src.bottom,
};
// apply the transform to them.
matrix.transform(corners, 0, corners, 0, 4);
// now put the result in the rect. We take the min/max of Xs and min/max of Ys
dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
}
}