/*
*
* Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
package com.sun.pisces;
import com.sun.midp.main.Configuration;
/**
*
* @version
*/
public final class PiscesRenderer extends PathSink {
public static final int ARC_OPEN = 0;
public static final int ARC_CHORD = 1;
public static final int ARC_PIE = 2;
// IMPL NOTE - use Perseus conventions
public static final int COMMAND_MOVE_TO = 0;
public static final int COMMAND_LINE_TO = 1;
public static final int COMMAND_QUAD_TO = 2;
public static final int COMMAND_CUBIC_TO = 3;
public static final int COMMAND_CLOSE = 4;
private static final boolean enableLogging = false;
private static java.io.PrintStream logStream = null;
private static final int STROKE_X_BIAS;
private static final int STROKE_Y_BIAS;
static {
if (enableLogging) {
//String s = System.getProperty("piscesLogFile");
//if (s != null) {
try {
logStream = System.out;
//new java.io.PrintStream(new java.io.FileOutputStream(s));
} catch (Exception e) {
System.err.println("Error using System.out: " + e);
//System.err.println("Error opening log file: " + e);
}
//}
}
String strValue;
int intValue;
strValue = Configuration.getProperty("pisces.stroke.xbias");
intValue = 0; // default x bias
if (strValue != null) {
try {
intValue = Integer.parseInt(strValue);
} catch (NumberFormatException e) {
}
}
STROKE_X_BIAS = intValue;
strValue = Configuration.getProperty("pisces.stroke.ybias");
intValue = 0; // default y bias
if (strValue != null) {
try {
intValue = Integer.parseInt(strValue);
} catch (NumberFormatException e) {
}
}
STROKE_Y_BIAS = intValue;
}
private static boolean messageShown = false;
private static int DEFAULT_FILLER_FLATNESS = 1 << 15;
Object data = null;
int width, height;
int offset, scanlineStride, pixelStride;
int type;
RendererBase rdr;
public PathSink fillerP = null;
public PathSink textFillerP = null;
public PathSink strokerP = null;
PathSink externalConsumer;
boolean inSubpath = false;
boolean isPathFilled = false;
int lineWidth = 1 << 16;
int capStyle = 0;
int joinStyle = 0;
int miterLimit = 10 << 16;
int[] dashArray = null;
int dashPhase = 0;
Transform6 transform = new Transform6();
Paint paint;
Transform6 paintTransform;
Transform6 paintCompoundTransform;
int[] gcm_fractions = null;
int[] gcm_rgba = null;
int gcm_cycleMethod = -1;
GradientColorMap gradientColorMap = null;
int red = 0;
int green = 0;
int blue = 0;
int alpha = 255;
// Current bounding box for all primitives
int bbMinX = Integer.MIN_VALUE;
int bbMinY = Integer.MIN_VALUE;
int bbMaxX = Integer.MAX_VALUE;
int bbMaxY = Integer.MAX_VALUE;
/**
* Creates a renderer that will write into a given pixel array.
*
* @param data an <code>int</code> or <code>short</code> array
* where pixel data should be written.
* @param width the width of the pixel array.
* @param height the height of the pixel array.
* @param offset the starting offset of the pixel array.
* @param scanlineStride the scanline stride of the pixel array, in array
* entries.
* @param pixelStride the pixel stride of the pixel array, in array
* entries.
* @param type the pixel format, one of the
* <code>RendererBase.TYPE_*</code> constants.
*/
public PiscesRenderer(Object data, int width, int height,
int offset, int scanlineStride, int pixelStride,
int type) {
if (!messageShown) {
System.out.println("Using Pisces Renderer (java version)");
}
if (data instanceof NativeSurface) {
NativeSurface ns = (NativeSurface)data;
this.data = ns.getData();
this.width = ns.getWidth();
this.height = ns.getHeight();
this.offset = 0;
this.scanlineStride = ns.getWidth();
this.pixelStride = 1;
} else {
this.data = data;
this.width = width;
this.height = height;
this.offset = offset;
this.scanlineStride = scanlineStride;
this.pixelStride = pixelStride;
}
this.type = type;
this.rdr = new Renderer(this.data, this.width, this.height,
this.offset, this.scanlineStride, this.pixelStride,
type);
invalidate();
setFill();
messageShown = true;
}
private void invalidate() {
fillerP = null;
textFillerP = null;
strokerP = null;
}
private boolean antialiasingOn = true;
public void setAntialiasing(boolean antialiasingOn) {
this.antialiasingOn = antialiasingOn;
int samples = antialiasingOn ? 3 : 0;
rdr.setAntialiasing(samples, samples);
invalidate();
}
public boolean getAntialiasing() {
return this.antialiasingOn;
}
/**
* Sets the current paint color.
*
* @red a value between 0 and 255.
* @green a value between 0 and 255.
* @blue a value between 0 and 255.
* @alpha a value between 0 and 255.
*/
public void setColor(int red, int green, int blue, int alpha) {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.setColor(" +
red + ", " +
green + ", " +
blue + ", " +
alpha + ");");
}
}
rdr.setColor(red, green, blue, alpha);
this.red = red;
this.green = green;
this.blue = blue;
this.alpha = alpha;
this.paint = null;
}
/**
* Sets the current paint color. An alpha value of 255 is used.
*
* @red a value between 0 and 255.
* @green a value between 0 and 255.
* @blue a value between 0 and 255.
*/
public void setColor(int red, int green, int blue) {
setColor(red, green, blue, 255);
}
private boolean arraysDiffer(int[] a, int[] b) {
if (a == null) {
return true;
}
int len = b.length;
if (a.length != len) {
return true;
}
for (int i = 0; i < len; i++) {
if (a[i] != b[i]) {
return true;
}
}
return false;
}
private int[] cloneArray(int[] src) {
int len = src.length;
int[] dst = new int[len];
System.arraycopy(src, 0, dst, 0, len);
return dst;
}
private void setGradientColorMap(int[] fractions, int[] rgba,
int cycleMethod) {
if (fractions.length != rgba.length) {
throw new IllegalArgumentException("fractions.length != rgba.length!");
}
if (gradientColorMap == null ||
gcm_cycleMethod != cycleMethod ||
arraysDiffer(gcm_fractions, fractions) ||
arraysDiffer(gcm_rgba, rgba)) {
this.gradientColorMap =
new GradientColorMap(fractions, rgba, cycleMethod);
this.gcm_cycleMethod = cycleMethod;
this.gcm_fractions = cloneArray(fractions);
this.gcm_rgba = cloneArray(rgba);
}
}
private void setPaintTransform(Transform6 paintTransform) {
this.paintTransform = new Transform6(paintTransform);
this.paintCompoundTransform = new Transform6(paintTransform);
}
public void setLinearGradient(int x0, int y0, int x1, int y1,
int[] fractions, int[] rgba,
int cycleMethod,
Transform6 gradientTransform) {
setPaintTransform(gradientTransform);
setGradientColorMap(fractions, rgba, cycleMethod);
this.paint = new LinearGradient(x0, y0, x1, y1,
paintCompoundTransform,
gradientColorMap);
rdr.setPaint(paint);
}
public void setRadialGradient(int cx, int cy, int fx, int fy,
int radius,
int[] fractions, int[] rgba,
int cycleMethod,
Transform6 gradientTransform) {
setPaintTransform(gradientTransform);
setGradientColorMap(fractions, rgba, cycleMethod);
this.paint = new RadialGradient(cx, cy, fx, fy, radius,
paintCompoundTransform,
gradientColorMap);
rdr.setPaint(paint);
}
public void setTexture(int imageType,
Object imageData,
int width, int height,
int offset, int stride,
Transform6 textureTransform,
boolean repeat) {
Transform6 textureCompoundTransform = new Transform6(this.transform);
textureCompoundTransform.postMultiply(textureTransform);
setPaintTransform(textureCompoundTransform);
this.paint = new Texture(imageType, imageData,
width, height, offset, stride,
textureCompoundTransform, repeat);
rdr.setPaint(paint);
}
public Flattener fillFlattener = new Flattener();
Transformer fillTransformer = new Transformer();
public Flattener textFlattener = new Flattener();
Transformer textTransformer = new Transformer();
Stroker strokeStroker = new Stroker();
Dasher strokeDasher = new Dasher();
public Flattener strokeFlattener = new Flattener();
Transformer strokeTransformer = new Transformer();
public PathSink getStroker() {
if (this.strokerP == null) {
strokeStroker.setOutput(rdr);
strokeStroker.setParameters(lineWidth,
capStyle, joinStyle,
miterLimit, transform);
if (dashArray == null) {
strokeFlattener.setOutput(strokeStroker);
strokeFlattener.setFlatness(1 << 16);
} else {
strokeDasher.setOutput(strokeStroker);
strokeDasher.setParameters(dashArray, dashPhase, transform);
strokeFlattener.setOutput(strokeDasher);
strokeFlattener.setFlatness(1 << 16);
}
Transform6 t = transform;
t = new Transform6(transform);
t.m02 += STROKE_X_BIAS;
t.m12 += STROKE_Y_BIAS;
strokeTransformer.setTransform(t);
strokeTransformer.setOutput(strokeFlattener);
this.strokerP = strokeTransformer;
}
return strokerP;
}
public PathSink getFiller() {
if (this.fillerP == null) {
fillFlattener.setOutput(rdr);
String flatness = System.getProperty("filler.flatness");
if (flatness != null) {
fillFlattener.setFlatness(Integer.parseInt(flatness));
} else {
fillFlattener.setFlatness(DEFAULT_FILLER_FLATNESS);
}
fillTransformer.setOutput(fillFlattener);
fillTransformer.setTransform(transform);
this.fillerP = fillTransformer;
}
return fillerP;
}
public PathSink getTextFiller() {
if (textFillerP == null) {
textFlattener.setOutput(rdr);
textFlattener.setFlatness(1 << (16 -
Math.min(rdr.getSubpixelLgPositionsX(),
rdr.getSubpixelLgPositionsY())));
textTransformer.setOutput(textFlattener);
textTransformer.setTransform(transform);
this.textFillerP = textTransformer;
}
return textFillerP;
}
/**
* Sets the current stroke parameters.
*
* @param lineWidth the sroke width, in S15.16 format.
* @param capStyle the line cap style, one of
* <code>Stroker.CAP_*</code>.
* @param joinStyle the line cap style, one of
* <code>Stroker.JOIN_*</code>.
* @param miterLimit the stroke miter limit, in S15.16 format.
* @param dashArray an <code>int</code> array containing the dash
* segment lengths in S15.16 format, or <code>null</code>.
* @param dashPhase the starting dash offset, in S15.16 format.
*/
public void setStroke(int lineWidth, int capStyle, int joinStyle,
int miterLimit, int[] dashArray, int dashPhase) {
if (enableLogging) {
if (logStream != null) {
logStream.print("dashArray = ");
if (dashArray == null) {
logStream.println("null;");
} else {
logStream.print("{");
for (int i = 0; i < dashArray.length; i++) {
logStream.print(" " + dashArray[i]);
}
logStream.print(" };");
}
logStream.println("pr.setStroke(" +
lineWidth + ", " +
capStyle + ", " +
joinStyle + ", " +
miterLimit + ", " +
"dashArray, " +
dashPhase + ");");
}
}
this.lineWidth = lineWidth;
this.capStyle = capStyle;
this.joinStyle = joinStyle;
this.miterLimit = miterLimit;
this.dashArray = dashArray;
this.dashPhase = dashPhase;
this.strokerP = null;
setStroke();
}
/**
* Sets the current transform from user to window coordinates.
*
* @param transform an <code>Transform6</code> object.
*/
public void setTransform(Transform6 transform) {
if (enableLogging) {
if (logStream != null) {
logStream.println("transform = new Transform6(" +
transform.m00 + ", " +
transform.m01 + ", " +
transform.m10 + ", " +
transform.m11 + ", " +
transform.m02 + ", " +
transform.m12 + ");");
logStream.println("pr.setTransform(transform);");
}
}
this.transform = transform;
if (paint != null) {
setPaintTransform(paintTransform);
paint.setTransform(this.paintCompoundTransform);
rdr.setPaint(paint);
}
invalidate();
}
public Transform6 getTransform() {
return new Transform6(transform);
}
/**
* Sets a clip rectangle for all primitives. Each primitive will be
* clipped to the intersection of this rectangle and the destination
* image bounds.
*/
public void setClip(int minX, int minY, int width, int height) {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.setClip(" +
minX + ", " +
minY + ", " +
width + ", " +
height + ");");
}
}
this.bbMinX = minX;
this.bbMinY = minY;
this.bbMaxX = minX + width;
this.bbMaxY = minY + height;
}
/**
* Resets the clip rectangle. Each primitive will be clipped only
* to the destination image bounds.
*/
public void resetClip() {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.resetClip()");
}
}
this.bbMinX = Integer.MIN_VALUE;
this.bbMinY = Integer.MIN_VALUE;
this.bbMaxX = Integer.MAX_VALUE;
this.bbMaxY = Integer.MAX_VALUE;
}
public void beginRendering(int windingRule) {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.beginRendering(" + windingRule + ")");
}
}
myBeginRendering(windingRule);
}
private void myBeginRendering(int windingRule) {
int minX = Math.max(0, bbMinX);
int minY = Math.max(0, bbMinY);
int maxX = Math.min(width, bbMaxX);
int maxY = Math.min(height, bbMaxY);
myBeginRendering(minX, minY, maxX - minX, maxY - minY, windingRule);
}
/**
* Begins the rendering of path data. The supplied clipping
* bounds are intersected against the current clip rectangle and
* the destination image bounds; only pixels within the resulting
* rectangle may be written to.
*/
public void beginRendering(int minX, int minY, int width, int height,
int windingRule) {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.beginRendering(" +
minX + ", " +
minY + ", " +
width + ", " +
height + ", " +
windingRule + ");");
}
}
myBeginRendering(minX, minY, width, height, windingRule);
}
private void myBeginRendering(int minX, int minY, int width, int height,
int windingRule) {
inSubpath = false;
int maxX = minX + width;
int maxY = minY + height;
minX = Math.max(minX, 0);
minX = Math.max(minX, bbMinX);
minY = Math.max(minY, 0);
minY = Math.max(minY, bbMinY);
maxX = Math.min(maxX, this.width);
maxX = Math.min(maxX, bbMaxX);
maxY = Math.min(maxY, this.height);
maxY = Math.min(maxY, bbMaxY);
width = maxX - minX;
height = maxY - minY;
rdr.beginRendering(minX, minY, width, height, windingRule);
}
public void moveTo(int x0, int y0) {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.moveTo(" + x0 + ", " + y0 + ");");
}
}
if (inSubpath && isPathFilled) {
externalConsumer.close();
}
inSubpath = false;
externalConsumer.moveTo(x0, y0);
}
public void lineTo(int x1, int y1) {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.lineTo(" + x1 + ", " + y1 + ");");
}
}
inSubpath = true;
externalConsumer.lineTo(x1, y1);
}
public void lineJoin() {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.lineJoin();");
}
}
externalConsumer.lineJoin();
}
public void quadTo(int x1, int y1, int x2, int y2) {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.quadTo(" +
x1 + ", " + y1 + ", " +
x2 + ", " + y2 + ");");
}
}
inSubpath = true;
externalConsumer.quadTo(x1, y1, x2, y2);
}
public void cubicTo(int x1, int y1, int x2, int y2, int x3, int y3) {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.cubicTo(" +
x1 + ", " + y1 + ", " +
x2 + ", " + y2 + ", " +
x3 + ", " + y3 + ");");
}
}
inSubpath = true;
externalConsumer.cubicTo(x1, y1, x2, y2, x3, y3);
}
public void close() {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.close();");
}
}
inSubpath = false;
externalConsumer.close();
}
public void end() {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.end();");
}
}
if (inSubpath && isPathFilled) {
close();
}
inSubpath = false;
externalConsumer.end();
}
/**
* Completes the rendering of path data. Destination pixels will
* be written at this time.
*/
public void endRendering() {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.endRendering();");
}
}
end();
myEndRendering();
}
private void myEndRendering() {
rdr.endRendering();
}
private void renderPath(int numCommands,
byte[] commands,
float[] coordsXY,
int windingRule) {
beginRendering(windingRule);
int clen = coordsXY.length;
int offset = 0;
for (int i = 0; i < numCommands; i++) {
int command = commands[i] & 0xff;
switch (command) {
case COMMAND_MOVE_TO:
if (offset >= 0 & offset < clen - 1) {
int x0 = (int)(coordsXY[offset++]*65536.0f);
int y0 = (int)(coordsXY[offset++]*65536.0f);
moveTo(x0, y0);
}
break;
case COMMAND_LINE_TO:
if (offset >= 0 & offset < clen - 1) {
int x1 = (int)(coordsXY[offset++]*65536.0f);
int y1 = (int)(coordsXY[offset++]*65536.0f);
lineTo(x1, y1);
}
break;
case COMMAND_QUAD_TO:
if (offset >= 0 & offset < clen - 3) {
int x1 = (int)(coordsXY[offset++]*65536.0f);
int y1 = (int)(coordsXY[offset++]*65536.0f);
int x2 = (int)(coordsXY[offset++]*65536.0f);
int y2 = (int)(coordsXY[offset++]*65536.0f);
quadTo(x1, y1, x2, y2);
}
break;
case COMMAND_CUBIC_TO:
if (offset >= 0 & offset < clen - 5) {
int x1 = (int)(coordsXY[offset++]*65536.0f);
int y1 = (int)(coordsXY[offset++]*65536.0f);
int x2 = (int)(coordsXY[offset++]*65536.0f);
int y2 = (int)(coordsXY[offset++]*65536.0f);
int x3 = (int)(coordsXY[offset++]*65536.0f);
int y3 = (int)(coordsXY[offset++]*65536.0f);
cubicTo(x1, y1, x2, y2, x3, y3);
}
break;
case COMMAND_CLOSE:
close();
break;
}
}
endRendering();
}
/**
* Render a complex path, possibly caching the results in a form
* that can be rendered more rapidly at a future time. The cache
* will be valid across changes in paint style, but not across
* changes to the transform, stroke/fill mode setting, stroke
* parameters, or winding rule.
* <p> The implementation does not check the validity of the cache
* relative to changes in the renderer state. It is up to the
* caller to manually invalidate the cache object as needed. The
* other parameters must contain a valid description of the path
* even if a valid cache is passed in. If <code>cache</code> is
* <code>null</code>, no caching is performed.
*
* <p> This method is equivalent to:
*
* <pre>
* beginRendering(windingRule);
*
* PiscesCache cache = getCache();
* if (cache != null) {
* if (cache.isValid()) {
* // Render using the cached form of the path
* renderFromCache(cache);
* } else {
* // Perform rendering and optionally place a pre-renderered
* // representation of the results into the cache
* renderAndComputeCache(numCommands, commands, offsets, coordsXY,
* windingRule, cache);
* }
* } else {
* // Perform rendering without a cache
* renderNoCache(numCommands, commands, offsets, coordsXY, windingRule);
* }
*
* endRendering();
* </pre>
*
* <p> Any command for which the value of <code>offsets</code>
* would lead to a reference outside of the bounds of
* <code>coordsXY</code> will not be issued.
*
* <p> Retrieval of the bounding box using <code>getBoundingBox</code>
* following a call to <code>render</code> is supported.
*/
public void renderPath(int numCommands,
byte[] commands,
float[] coordsXY,
int windingRule,
PiscesCache cache) {
if (cache != null) {
if (cache.isValid()) {
rdr.renderFromCache(cache);
} else {
rdr.setCache(cache);
renderPath(numCommands, commands, coordsXY, windingRule);
rdr.setCache(null);
}
} else {
renderPath(numCommands, commands, coordsXY, windingRule);
}
}
/**
* Returns a bounding box containing all pixels drawn during the
* rendering of the most recent primitive
* (beginRendering/endRendering pair). The bounding box is
* returned in the form (x, y, width, height).
*/
public void getBoundingBox(int[] bbox) {
if (enableLogging) {
if (logStream != null) {
logStream.println("bbox = new int[4];");
logStream.println("pr.getBoundingBox(bbox);");
}
}
rdr.getBoundingBox(bbox);
}
public void setStroke() {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.setStroke();");
}
}
isPathFilled = false;
this.externalConsumer = getStroker();
}
public void setFill() {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.setFill();");
}
}
isPathFilled = true ;
this.externalConsumer = getFiller();
}
public void setTextFill() {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.setTextFill();");
}
}
isPathFilled = true;
this.externalConsumer = getTextFiller();
}
public void drawLine(int x0, int y0, int x1, int y1) {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.drawLine(" +
x0 + ", " +
y0 + ", " +
x1 + ", " +
y1 + ");");
}
}
myBeginRendering(RendererBase.WIND_NON_ZERO);
PathSink stroker = getStroker();
stroker.moveTo(x0, y0);
stroker.lineTo(x1, y1);
stroker.end();
myEndRendering();
}
private static int[] convert8To5;
private static int[] convert8To6;
static {
convert8To5 = new int[256];
convert8To6 = new int[256];
for (int i = 0; i < 256; i++) {
convert8To5[i] = (i*31 + 127)/255;
convert8To6[i] = (i*63 + 127)/255;
}
}
/**
*
* @param x the X coordinate in S15.16 format.
* @param y the Y coordinate in S15.16 format.
* @param w the width in S15.16 format.
* @param h the height in S15.16 format.
*/
public void fillRect(int x, int y, int w, int h) {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.fillRect(" +
x + ", " +
y + ", " +
w + ", " +
h + ");");
}
}
if (w <= 0 || h <= 0) {
return;
}
// Renderer will detect aligned rectangles
PathSink filler = getFiller();
fillOrDrawRect(filler, x, y, w, h);
}
public void drawRect(int x, int y, int w, int h) {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.drawRect(" +
x + ", " +
y + ", " +
w + ", " +
h + ");");
}
}
if (w <= 0 || h <= 0) {
return;
}
// If dashing is disabled, and using mitered joins,
// simply draw two opposing rect outlines separated
// by linewidth
if (dashArray == null) {
if ((joinStyle == Stroker.JOIN_MITER)
&& (miterLimit >= PiscesMath.SQRT_TWO)) {
int x0 = x + STROKE_X_BIAS;
int y0 = y + STROKE_Y_BIAS;
int x1 = x0 + w;
int y1 = y0 + h;
int lw = lineWidth;
int m = lineWidth/2;
PathSink filler = getFiller();
myBeginRendering(RendererBase.WIND_NON_ZERO);
filler.moveTo(x0 - m, y0 - m);
filler.lineTo(x1 + m, y0 - m);
filler.lineTo(x1 + m, y1 + m);
filler.lineTo(x0 - m, y1 + m);
filler.close();
// Hollow out interior if w and h are greater than linewidth
if ((x1 - x0) > lw && (y1 - y0) > lw) {
filler.moveTo(x0 + m, y0 + m);
filler.lineTo(x0 + m, y1 - m);
filler.lineTo(x1 - m, y1 - m);
filler.lineTo(x1 - m, y0 + m);
filler.close();
}
filler.end();
myEndRendering();
return;
} else if (joinStyle == Stroker.JOIN_ROUND) {
// IMPL NOTE - accelerate hollow rects with round joins
}
}
PathSink stroker = getStroker();
fillOrDrawRect(stroker, x, y, w, h);
}
private void fillOrDrawRect(PathSink consumer,
int x, int y, int w, int h) {
int x0 = x;
int y0 = y;
int x1 = x0 + w;
int y1 = y0 + h;
myBeginRendering(RendererBase.WIND_NON_ZERO);
consumer.moveTo(x0, y0);
consumer.lineTo(x1, y0);
consumer.lineTo(x1, y1);
consumer.lineTo(x0, y1);
consumer.close();
consumer.end();
myEndRendering();
}
public void drawOval(int x, int y, int w, int h) {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.drawOval(" +
x + ", " +
y + ", " +
w + ", " +
h + ");");
}
}
fillOrDrawOval(x, y, w, h, true);
}
public void fillOval(int x, int y, int w, int h) {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.fillOval(" +
x + ", " +
y + ", " +
w + ", " +
h + ");");
}
}
fillOrDrawOval(x, y, w, h, false);
}
// Emit quarter-arc about a central point (cx, cy).
// Each quadrant is suitably reflected
private void emitQuadrants(PathSink consumer, int cx, int cy,
int[] points, int nPoints) {
// Emit quarter-arc once for each quadrant, suitably
// reflected
for (int pass = 0; pass < 4; pass++) {
int xsign = 1;
int ysign = 1;
if (pass == 1 || pass == 2) xsign = -1;
if (pass == 2 || pass == 3) ysign = -1;
int incr = 2*xsign*ysign;
int idx = (incr > 0) ? 0 : 2*nPoints - 2;
for (int j = 0; j < nPoints; j++) {
consumer.lineTo(cx + xsign*points[idx],
cy + ysign*points[idx + 1]);
idx += incr;
}
}
}
private void emitOval(PathSink consumer,
int cx, int cy, int rx, int ry,
int nPoints, boolean reverse) {
int i = reverse ? nPoints - 1 : 1;
int incr = reverse ? -1 : 1;
consumer.moveTo(cx + rx, cy);
nPoints /= 4;
int[] points = new int[2*nPoints];
int idx = 0;
for (int j = 0; j < nPoints; j++) {
int theta = i*PiscesMath.TWO_PI/(4*nPoints);
int ox = PiscesMath.cos(theta);
int oy = PiscesMath.sin(theta);
points[idx++] = (int)((long)rx*ox >> 16);
points[idx++] = (int)((long)ry*oy >> 16);
i += incr;
}
emitQuadrants(consumer, cx, cy, points, nPoints);
consumer.close();
}
// Emit the outline of an oval, offset by pen radius lw2
// The interior path may self-intersect, but this is handled
// by using a WIND_NON_ZERO wining rule
private void emitOffsetOval(PathSink consumer,
int cx, int cy, int rx, int ry,
int lw2,
int nPoints, boolean inside) {
int i = inside ? nPoints - 1 : 1;
int incr = inside ? -1 : 1;
double drx = rx/65536.0;
double dry = ry/65536.0;
double dlw2 = lw2/65536.0;
consumer.moveTo(cx + rx + lw2*incr, cy);
nPoints /= 4;
int[] points = new int[2*nPoints];
int idx = 0;
for (int j = 0; j < nPoints; j++) {
double dtheta = i*(Math.PI/2.0)/nPoints;
double cosTheta = Math.cos(dtheta);
double sinTheta = Math.sin(dtheta);
double drxSinTheta = drx*sinTheta;
double dryCosTheta = dry*cosTheta;
double den = dlw2/Math.sqrt(drxSinTheta*drxSinTheta +
dryCosTheta*dryCosTheta);
double dpx = cosTheta*(drx + incr*dry*den);
double dpy = sinTheta*(dry + incr*drx*den);
int px = (int)(dpx*65536.0);
int py = (int)(dpy*65536.0);
points[idx++] = px;
points[idx++] = py;
i += incr;
}
emitQuadrants(consumer, cx, cy, points, nPoints);
consumer.close();
}
private void fillOrDrawOval(int x, int y, int w, int h,
boolean hollow) {
if (w <= 0 || h <= 0) {
return;
}
// if (!antialiasingOn) {
// lineWidth = (lineWidth + 0xffff) & 0xffff0000;
// }
int w2 = w >> 1;
int h2 = h >> 1;
int cx = x + w2;
int cy = y + h2;
int lineWidth2 = hollow ? lineWidth/2 : 0;
int wl = w2 + lineWidth2;
int hl = h2 + lineWidth2;
int nPoints = Math.max(16, Math.max(wl, hl) >> 13);
myBeginRendering(RendererBase.WIND_NON_ZERO);
// Stroke the outline if dashing
if (hollow && dashArray != null) {
PathSink stroker = getStroker();
emitOval(stroker, cx, cy, w2, h2, nPoints, false);
stroker.end();
myEndRendering();
return;
}
if (!antialiasingOn) {
cx += STROKE_X_BIAS;
}
// Draw exterior outline
PathSink filler = getFiller();
if (w == h) {
emitOval(filler, cx, cy, wl, hl, nPoints, false);
} else {
emitOffsetOval(filler, cx, cy, w2, h2, lineWidth2,
nPoints, false);
}
// Draw interior in the reverse direction
if (hollow) {
wl = w2 - lineWidth2;
hl = h2 - lineWidth2;
if (wl > 0 && hl > 0) {
if (w == h) {
emitOval(filler, cx, cy, wl, hl, nPoints, true);
} else {
emitOffsetOval(filler, cx, cy, w2, h2, lineWidth2,
nPoints, true);
}
}
}
filler.end();
myEndRendering();
}
private static final long acv = (long)(65536.0*0.22385762508460333);
public void fillRoundRect(int x, int y, int w, int h,
int aw, int ah) {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.fillRoundRect(" +
x + ", " +
y + ", " +
w + ", " +
h + ", " +
aw + ", " +
ah + ");");
}
}
if (w <= 0 || h <= 0) {
return;
}
fillOrDrawRoundRect(x, y, w, h, aw, ah, false);
}
public void drawRoundRect(int x, int y, int w, int h,
int aw, int ah) {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.drawRoundRect(" +
x + ", " +
y + ", " +
w + ", " +
h + ", " +
aw + ", " +
ah + ");");
}
}
if (w < 0 || h < 0) {
return;
}
fillOrDrawRoundRect(x, y, w, h, aw, ah, true);
}
// Args are S15.16
private void emitRoundRect(PathSink consumer,
int x, int y, int w, int h, int aw, int ah,
boolean reverse) {
int xw = x + w;
int yh = y + h;
int aw2 = aw >> 1;
int ah2 = ah >> 1;
int acvaw = (int)(acv*aw >> 16);
int acvah = (int)(acv*ah >> 16);
int xacvaw = x + acvaw;
int xw_acvaw = xw - acvaw;
int yacvah = y + acvah;
int yh_acvah = yh - acvah;
int xaw2 = x + aw2;
int xw_aw2 = xw - aw2;
int yah2 = y + ah2;
int yh_ah2 = yh - ah2;
consumer.moveTo(x, yah2);
if (reverse) {
consumer.cubicTo(x, yacvah, xacvaw, y, xaw2, y);
consumer.lineTo(xw_aw2, y);
consumer.cubicTo(xw_acvaw, y, xw, yacvah, xw, yah2);
consumer.lineTo(xw, yh_ah2);
consumer.cubicTo(xw, yh_acvah, xw_acvaw, yh, xw_aw2, yh);
consumer.lineTo(xaw2, yh);
consumer.cubicTo(xacvaw, yh, x, yh_acvah, x, yh_ah2);
} else {
consumer.lineTo(x, yh_ah2);
consumer.cubicTo(x, yh_acvah, xacvaw, yh, xaw2, yh);
consumer.lineTo(xw_aw2, yh);
consumer.cubicTo(xw_acvaw, yh, xw, yh_acvah, xw, yh_ah2);
consumer.lineTo(xw, yah2);
consumer.cubicTo(xw, yacvah, xw_acvaw, y, xw_aw2, y);
consumer.lineTo(xaw2, y);
consumer.cubicTo(xacvaw, y, x, yacvah, x, yah2);
}
consumer.close();
consumer.end();
}
private void fillOrDrawRoundRect(int x, int y, int w, int h,
int aw, int ah,
boolean stroke) {
PathSink consumer = stroke ? getStroker() : getFiller();
if (aw < 0) aw = -aw;
if (aw > w) aw = w;
if (ah < 0) ah = -ah;
if (ah > h) ah = h;
// If stroking but not dashing, draw the outer and inner
// contours explicitly as round rects
//
// Note - this only works if aw == ah since the result of tracing
// a circle with a circular pen is a larger circle, but the result
// of tracing an ellipse with a circular pen is not (generally)
// an ellipse...
if (stroke && dashArray == null && aw == ah) {
int lineWidth2 = lineWidth >> 1;
myBeginRendering(RendererBase.WIND_NON_ZERO);
PathSink filler = getFiller();
x += STROKE_X_BIAS;
y += STROKE_Y_BIAS;
emitRoundRect(filler,
x - lineWidth2, y - lineWidth2,
w + lineWidth, h + lineWidth,
aw + lineWidth, ah + lineWidth, false);
// Empty out inner rect
w -= lineWidth;
h -= lineWidth;
if (w > 0 && h > 0) {
emitRoundRect(filler,
x + lineWidth2, y + lineWidth2,
w, h,
aw - lineWidth, ah - lineWidth, true);
}
myEndRendering();
} else {
myBeginRendering(RendererBase.WIND_NON_ZERO);
emitRoundRect(consumer, x, y, w, h, aw, ah, false);
myEndRendering();
}
}
public void drawArc(int x, int y, int width, int height,
int startAngle, int arcAngle, int arcType) {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.drawArc(" +
x + ", " +
y + ", " +
width + ", " +
height + ", " +
startAngle + ", " +
arcAngle + ", " +
arcType + ");");
}
}
fillOrDrawArc(x, y, width, height, startAngle, arcAngle, arcType, true);
}
public void fillArc(int x, int y, int width, int height,
int startAngle, int arcAngle, int arcType) {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.fillArc(" +
x + ", " +
y + ", " +
width + ", " +
height + ", " +
startAngle + ", " +
arcAngle + ", " +
arcType + ");");
}
}
fillOrDrawArc(x, y, width, height, startAngle, arcAngle, arcType, false);
}
private void emitArc(PathSink consumer,
int cx, int cy, int rx, int ry,
int startAngle, int endAngle,
int nPoints) {
boolean first = true;
for (int i = 0; i < nPoints; i++) {
int theta = startAngle + i*(endAngle - startAngle)/(nPoints - 1);
int ox = PiscesMath.cos(theta);
int oy = PiscesMath.sin(theta);
int lx = cx + (int)((long)rx*ox >> 16);
int ly = cy - (int)((long)ry*oy >> 16);
if (first) {
consumer.moveTo(lx, ly);
first = false;
} else {
consumer.lineTo(lx, ly);
}
}
}
public void fillOrDrawArc(int x, int y, int width, int height,
int startAngle, int arcAngle, int arcType,
boolean stroke) {
PathSink consumer = stroke ? getStroker() : getFiller();
if (width <= 0 || height <= 0) {
return;
}
int w2 = width >> 1;
int h2 = height >> 1;
int cx = x + w2;
int cy = y + h2;
startAngle = (int)(((long)startAngle*PiscesMath.TWO_PI)/(360*65536));
arcAngle = (int)(((long)arcAngle*PiscesMath.TWO_PI)/(360*65536));
int endAngle = startAngle + arcAngle;
int nPoints = Math.max(16, Math.max(w2, h2) >> 16);
myBeginRendering(RendererBase.WIND_NON_ZERO);
emitArc(consumer, cx, cy, w2, h2, startAngle, endAngle, nPoints);
if (arcType == ARC_PIE) {
consumer.lineTo(cx, cy);
}
if (!stroke || (arcType == ARC_CHORD) || (arcType == ARC_PIE)) {
consumer.close();
}
consumer.end();
myEndRendering();
}
public void getImageData() {
rdr.getImageData(data, offset, scanlineStride);
}
public void clearRect(int x, int y, int w, int h) {
if (enableLogging) {
if (logStream != null) {
logStream.println("pr.clearRect(" +
x + ", " +
y + ", " +
w + ", " +
h + ");");
}
}
int maxX = x + w;
int maxY = y + h;
x = Math.max(x, 0);
x = Math.max(x, bbMinX);
y = Math.max(y, 0);
y = Math.max(y, bbMinY);
maxX = Math.min(maxX, this.width);
maxX = Math.min(maxX, bbMaxX);
maxY = Math.min(maxY, this.height);
maxY = Math.min(maxY, bbMaxY);
rdr.clearRect(x, y, maxX - x, maxY - y);
}
public void setPathData(float[] data, byte[] commands, int nCommands) {
throw new IllegalStateException("Not implemented");
}
}