提交 7d835a46 编写于 作者: D dlila

6967436: lines longer than 2^15 can fill window.

6967433: dashed lines broken when using scaling transforms.
Summary: converted pisces to floating point. Also, using better AA algorithm
Reviewed-by: flar
上级 2787aeb2
......@@ -36,117 +36,71 @@ package sun.java2d.pisces;
* semantics are unclear.
*
*/
public class Dasher extends LineSink {
public class Dasher implements LineSink {
private final LineSink output;
private final float[] dash;
private final float startPhase;
private final boolean startDashOn;
private final int startIdx;
LineSink output;
int[] dash;
int startPhase;
boolean startDashOn;
int startIdx;
private final float m00, m10, m01, m11;
private final float det;
int idx;
boolean dashOn;
int phase;
private boolean firstDashOn;
private boolean starting;
int sx, sy;
int x0, y0;
private int idx;
private boolean dashOn;
private float phase;
int m00, m01;
int m10, m11;
private float sx, sy;
private float x0, y0;
private float sx1, sy1;
Transform4 transform;
boolean symmetric;
long ldet;
boolean firstDashOn;
boolean starting;
int sx1, sy1;
/**
* Empty constructor. <code>setOutput</code> and
* <code>setParameters</code> must be called prior to calling any
* other methods.
*/
public Dasher() {}
/**
* Constructs a <code>Dasher</code>.
*
* @param output an output <code>LineSink</code>.
* @param dash an array of <code>int</code>s containing the dash
* pattern in S15.16 format.
* @param phase an <code>int</code> containing the dash phase in
* S15.16 format.
* @param dash an array of <code>int</code>s containing the dash pattern
* @param phase an <code>int</code> containing the dash phase
* @param transform a <code>Transform4</code> object indicating
* the transform that has been previously applied to all incoming
* coordinates. This is required in order to compute dash lengths
* properly.
*/
public Dasher(LineSink output,
int[] dash, int phase,
Transform4 transform) {
setOutput(output);
setParameters(dash, phase, transform);
}
/**
* Sets the output <code>LineSink</code> of this
* <code>Dasher</code>.
*
* @param output an output <code>LineSink</code>.
*/
public void setOutput(LineSink output) {
this.output = output;
}
/**
* Sets the parameters of this <code>Dasher</code>.
*
* @param dash an array of <code>int</code>s containing the dash
* pattern in S15.16 format.
* @param phase an <code>int</code> containing the dash phase in
* S15.16 format.
* @param transform a <code>Transform4</code> object indicating
* the transform that has been previously applied to all incoming
* coordinates. This is required in order to compute dash lengths
* properly.
*/
public void setParameters(int[] dash, int phase,
Transform4 transform) {
float[] dash, float phase,
float a00, float a01, float a10, float a11) {
if (phase < 0) {
throw new IllegalArgumentException("phase < 0 !");
}
this.output = output;
// Normalize so 0 <= phase < dash[0]
int idx = 0;
dashOn = true;
int d;
float d;
while (phase >= (d = dash[idx])) {
phase -= d;
idx = (idx + 1) % dash.length;
dashOn = !dashOn;
}
this.dash = new int[dash.length];
for (int i = 0; i < dash.length; i++) {
this.dash[i] = dash[i];
}
this.dash = dash;
this.startPhase = this.phase = phase;
this.startDashOn = dashOn;
this.startIdx = idx;
this.transform = transform;
this.m00 = transform.m00;
this.m01 = transform.m01;
this.m10 = transform.m10;
this.m11 = transform.m11;
this.ldet = ((long)m00*m11 - (long)m01*m10) >> 16;
this.symmetric = (m00 == m11 && m10 == -m01);
m00 = a00;
m01 = a01;
m10 = a10;
m11 = a11;
det = m00 * m11 - m01 * m10;
}
public void moveTo(int x0, int y0) {
public void moveTo(float x0, float y0) {
output.moveTo(x0, y0);
this.idx = startIdx;
this.dashOn = this.startDashOn;
......@@ -160,7 +114,7 @@ public class Dasher extends LineSink {
output.lineJoin();
}
private void goTo(int x1, int y1) {
private void goTo(float x1, float y1) {
if (dashOn) {
if (starting) {
this.sx1 = x1;
......@@ -180,52 +134,64 @@ public class Dasher extends LineSink {
this.y0 = y1;
}
public void lineTo(int x1, int y1) {
while (true) {
int d = dash[idx] - phase;
int lx = x1 - x0;
int ly = y1 - y0;
public void lineTo(float x1, float y1) {
// The widened line is squished to a 0 width one, so no drawing is done
if (det == 0) {
goTo(x1, y1);
return;
}
float dx = x1 - x0;
float dy = y1 - y0;
// Compute segment length in the untransformed
// coordinate system
// IMPL NOTE - use fixed point
int l;
if (symmetric) {
l = (int)((PiscesMath.hypot(lx, ly)*65536L)/ldet);
} else{
long la = ((long)ly*m00 - (long)lx*m10)/ldet;
long lb = ((long)ly*m01 - (long)lx*m11)/ldet;
l = (int)PiscesMath.hypot(la, lb);
float la = (dy*m00 - dx*m10)/det;
float lb = (dy*m01 - dx*m11)/det;
float origLen = (float) Math.hypot(la, lb);
if (origLen == 0) {
// Let the output LineSink deal with cases where dx, dy are 0.
goTo(x1, y1);
return;
}
if (l < d) {
// The scaling factors needed to get the dx and dy of the
// transformed dash segments.
float cx = dx / origLen;
float cy = dy / origLen;
while (true) {
float leftInThisDashSegment = dash[idx] - phase;
if (origLen < leftInThisDashSegment) {
goTo(x1, y1);
// Advance phase within current dash segment
phase += l;
phase += origLen;
return;
} else if (origLen == leftInThisDashSegment) {
goTo(x1, y1);
phase = 0f;
idx = (idx + 1) % dash.length;
dashOn = !dashOn;
return;
}
long t;
int xsplit, ysplit;
// // For zero length dashses, SE appears to move 1/8 unit
// // in device space
// if (d == 0) {
// double dlx = lx/65536.0;
// double dly = ly/65536.0;
// len = PiscesMath.hypot(dlx, dly);
// double dt = 1.0/(8*len);
// double dxsplit = (x0/65536.0) + dt*dlx;
// double dysplit = (y0/65536.0) + dt*dly;
// xsplit = (int)(dxsplit*65536.0);
// ysplit = (int)(dysplit*65536.0);
// } else {
t = ((long)d << 16)/l;
xsplit = x0 + (int)(t*(x1 - x0) >> 16);
ysplit = y0 + (int)(t*(y1 - y0) >> 16);
// }
goTo(xsplit, ysplit);
float dashx, dashy;
float dashdx = dash[idx] * cx;
float dashdy = dash[idx] * cy;
if (phase == 0) {
dashx = x0 + dashdx;
dashy = y0 + dashdy;
} else {
float p = (leftInThisDashSegment) / dash[idx];
dashx = x0 + p * dashdx;
dashy = y0 + p * dashdy;
}
goTo(dashx, dashy);
origLen -= (dash[idx] - phase);
// Advance to next dash segment
idx = (idx + 1) % dash.length;
dashOn = !dashOn;
......@@ -233,6 +199,7 @@ public class Dasher extends LineSink {
}
}
public void close() {
lineTo(sx, sy);
if (firstDashOn) {
......
......@@ -39,16 +39,16 @@ package sun.java2d.pisces;
* <code>LineSink</code> interface.
*
*/
public abstract class LineSink {
public interface LineSink {
/**
* Moves the current drawing position to the point <code>(x0,
* y0)</code>.
*
* @param x0 the X coordinate in S15.16 format
* @param y0 the Y coordinate in S15.16 format
* @param x0 the X coordinate
* @param y0 the Y coordinate
*/
public abstract void moveTo(int x0, int y0);
public void moveTo(float x0, float y0);
/**
* Provides a hint that the current segment should be joined to
......@@ -65,29 +65,29 @@ public abstract class LineSink {
* <p> Other <code>LineSink</code> classes should simply pass this
* hint to their output sink as needed.
*/
public abstract void lineJoin();
public void lineJoin();
/**
* Draws a line from the current drawing position to the point
* <code>(x1, y1)</code> and sets the current drawing position to
* <code>(x1, y1)</code>.
*
* @param x1 the X coordinate in S15.16 format
* @param y1 the Y coordinate in S15.16 format
* @param x1 the X coordinate
* @param y1 the Y coordinate
*/
public abstract void lineTo(int x1, int y1);
public void lineTo(float x1, float y1);
/**
* Closes the current path by drawing a line from the current
* drawing position to the point specified by the moset recent
* <code>moveTo</code> command.
*/
public abstract void close();
public void close();
/**
* Ends the current path. It may be necessary to end a path in
* order to allow end caps to be drawn.
*/
public abstract void end();
public void end();
}
/*
* Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 in the LICENSE file that
* accompanied this code).
*
* 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.java2d.pisces;
public class PiscesMath {
private PiscesMath() {}
private static final int SINTAB_LG_ENTRIES = 10;
private static final int SINTAB_ENTRIES = 1 << SINTAB_LG_ENTRIES;
private static int[] sintab;
public static final int PI = (int)(Math.PI*65536.0);
public static final int TWO_PI = (int)(2.0*Math.PI*65536.0);
public static final int PI_OVER_TWO = (int)((Math.PI/2.0)*65536.0);
public static final int SQRT_TWO = (int)(Math.sqrt(2.0)*65536.0);
static {
sintab = new int[SINTAB_ENTRIES + 1];
for (int i = 0; i < SINTAB_ENTRIES + 1; i++) {
double theta = i*(Math.PI/2.0)/SINTAB_ENTRIES;
sintab[i] = (int)(Math.sin(theta)*65536.0);
}
}
public static int sin(int theta) {
int sign = 1;
if (theta < 0) {
theta = -theta;
sign = -1;
}
// 0 <= theta
while (theta >= TWO_PI) {
theta -= TWO_PI;
}
// 0 <= theta < 2*PI
if (theta >= PI) {
theta = TWO_PI - theta;
sign = -sign;
}
// 0 <= theta < PI
if (theta > PI_OVER_TWO) {
theta = PI - theta;
}
// 0 <= theta <= PI/2
int itheta = (int)((long)theta*SINTAB_ENTRIES/(PI_OVER_TWO));
return sign*sintab[itheta];
}
public static int cos(int theta) {
return sin(PI_OVER_TWO - theta);
}
// public static double sqrt(double x) {
// double dsqrt = Math.sqrt(x);
// int ix = (int)(x*65536.0);
// Int Isqrt = Isqrt(Ix);
// Long Lx = (Long)(X*65536.0);
// Long Lsqrt = Lsqrt(Lx);
// System.Out.Println();
// System.Out.Println("X = " + X);
// System.Out.Println("Dsqrt = " + Dsqrt);
// System.Out.Println("Ix = " + Ix);
// System.Out.Println("Isqrt = " + Isqrt/65536.0);
// System.Out.Println("Lx = " + Lx);
// System.Out.Println("Lsqrt = " + Lsqrt/65536.0);
// Return Dsqrt;
// }
// From Ken Turkowski, _Fixed-Point Square Root_, In Graphics Gems V
public static int isqrt(int x) {
int fracbits = 16;
int root = 0;
int remHi = 0;
int remLo = x;
int count = 15 + fracbits/2;
do {
remHi = (remHi << 2) | (remLo >>> 30); // N.B. - unsigned shift R
remLo <<= 2;
root <<= 1;
int testdiv = (root << 1) + 1;
if (remHi >= testdiv) {
remHi -= testdiv;
root++;
}
} while (count-- != 0);
return root;
}
public static long lsqrt(long x) {
int fracbits = 16;
long root = 0;
long remHi = 0;
long remLo = x;
int count = 31 + fracbits/2;
do {
remHi = (remHi << 2) | (remLo >>> 62); // N.B. - unsigned shift R
remLo <<= 2;
root <<= 1;
long testDiv = (root << 1) + 1;
if (remHi >= testDiv) {
remHi -= testDiv;
root++;
}
} while (count-- != 0);
return root;
}
public static double hypot(double x, double y) {
// new RuntimeException().printStackTrace();
return Math.sqrt(x*x + y*y);
}
public static int hypot(int x, int y) {
return (int)((lsqrt((long)x*x + (long)y*y) + 128) >> 8);
}
public static long hypot(long x, long y) {
return (lsqrt(x*x + y*y) + 128) >> 8;
}
}
......@@ -37,24 +37,8 @@ import sun.java2d.pipe.RenderingEngine;
import sun.java2d.pipe.AATileGenerator;
public class PiscesRenderingEngine extends RenderingEngine {
public static Transform4 IdentT4 = new Transform4();
public static double defaultFlat = 0.1;
static int FloatToS15_16(float flt) {
flt = flt * 65536f + 0.5f;
if (flt <= -(65536f * 65536f)) {
return Integer.MIN_VALUE;
} else if (flt >= (65536f * 65536f)) {
return Integer.MAX_VALUE;
} else {
return (int) Math.floor(flt);
}
}
static float S15_16ToFloat(int fix) {
return (fix / 65536f);
}
/**
* Create a widened path as specified by the parameters.
* <p>
......@@ -85,18 +69,19 @@ public class PiscesRenderingEngine extends RenderingEngine {
strokeTo(src,
null,
width,
false,
caps,
join,
miterlimit,
dashes,
dashphase,
new LineSink() {
public void moveTo(int x0, int y0) {
p2d.moveTo(S15_16ToFloat(x0), S15_16ToFloat(y0));
public void moveTo(float x0, float y0) {
p2d.moveTo(x0, y0);
}
public void lineJoin() {}
public void lineTo(int x1, int y1) {
p2d.lineTo(S15_16ToFloat(x1), S15_16ToFloat(y1));
public void lineTo(float x1, float y1) {
p2d.lineTo(x1, y1);
}
public void close() {
p2d.closePath();
......@@ -144,12 +129,12 @@ public class PiscesRenderingEngine extends RenderingEngine {
{
strokeTo(src, at, bs, thin, normalize, antialias,
new LineSink() {
public void moveTo(int x0, int y0) {
consumer.moveTo(S15_16ToFloat(x0), S15_16ToFloat(y0));
public void moveTo(float x0, float y0) {
consumer.moveTo(x0, y0);
}
public void lineJoin() {}
public void lineTo(int x1, int y1) {
consumer.lineTo(S15_16ToFloat(x1), S15_16ToFloat(y1));
public void lineTo(float x1, float y1) {
consumer.lineTo(x1, y1);
}
public void close() {
consumer.closePath();
......@@ -181,6 +166,7 @@ public class PiscesRenderingEngine extends RenderingEngine {
strokeTo(src,
at,
lw,
normalize,
bs.getEndCap(),
bs.getLineJoin(),
bs.getMiterLimit(),
......@@ -258,6 +244,7 @@ public class PiscesRenderingEngine extends RenderingEngine {
void strokeTo(Shape src,
AffineTransform at,
float width,
boolean normalize,
int caps,
int join,
float miterlimit,
......@@ -265,32 +252,16 @@ public class PiscesRenderingEngine extends RenderingEngine {
float dashphase,
LineSink lsink)
{
Transform4 t4;
if (at == null || at.isIdentity()) {
t4 = IdentT4;
} else {
t4 = new Transform4(FloatToS15_16((float) at.getScaleX()),
FloatToS15_16((float) at.getShearX()),
FloatToS15_16((float) at.getShearY()),
FloatToS15_16((float) at.getScaleY()));
float a00 = 1f, a01 = 0f, a10 = 0f, a11 = 1f;
if (at != null && !at.isIdentity()) {
a00 = (float)at.getScaleX();
a01 = (float)at.getShearX();
a10 = (float)at.getShearY();
a11 = (float)at.getScaleY();
}
lsink = new Stroker(lsink,
FloatToS15_16(width),
caps,
join,
FloatToS15_16(miterlimit),
t4);
lsink = new Stroker(lsink, width, caps, join, miterlimit, a00, a01, a10, a11);
if (dashes != null) {
int fdashes[] = new int[dashes.length];
for (int i = 0; i < dashes.length; i++) {
fdashes[i] = FloatToS15_16(dashes[i]);
}
lsink = new Dasher(lsink,
fdashes,
FloatToS15_16(dashphase),
t4);
lsink = new Dasher(lsink, dashes, dashphase, a00, a01, a10, a11);
}
PathIterator pi = src.getPathIterator(at, defaultFlat);
......@@ -302,13 +273,11 @@ public class PiscesRenderingEngine extends RenderingEngine {
while (!pi.isDone()) {
switch (pi.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
lsink.moveTo(FloatToS15_16(coords[0]),
FloatToS15_16(coords[1]));
lsink.moveTo(coords[0], coords[1]);
break;
case PathIterator.SEG_LINETO:
lsink.lineJoin();
lsink.lineTo(FloatToS15_16(coords[0]),
FloatToS15_16(coords[1]));
lsink.lineTo(coords[0], coords[1]);
break;
case PathIterator.SEG_CLOSE:
lsink.lineJoin();
......@@ -378,17 +347,19 @@ public class PiscesRenderingEngine extends RenderingEngine {
int bbox[])
{
PiscesCache pc = PiscesCache.createInstance();
Renderer r = new Renderer();
r.setCache(pc);
r.setAntialiasing(3, 3);
r.beginRendering(clip.getLoX(), clip.getLoY(),
clip.getWidth(), clip.getHeight());
Renderer r;
if (bs == null) {
PathIterator pi = s.getPathIterator(at, defaultFlat);
r.setWindingRule(pi.getWindingRule());
r = new Renderer(3, 3,
clip.getLoX(), clip.getLoY(),
clip.getWidth(), clip.getHeight(),
pi.getWindingRule(), pc);
pathTo(pi, r);
} else {
r.setWindingRule(PathIterator.WIND_NON_ZERO);
r = new Renderer(3, 3,
clip.getLoX(), clip.getLoY(),
clip.getWidth(), clip.getHeight(),
PathIterator.WIND_NON_ZERO, pc);
strokeTo(s, at, bs, thin, normalize, true, r);
}
r.endRendering();
......
......@@ -25,629 +25,478 @@
package sun.java2d.pisces;
public class Renderer extends LineSink {
public static final int WIND_EVEN_ODD = 0;
public static final int WIND_NON_ZERO = 1;
// Initial edge list size
// IMPL_NOTE - restore size after growth
public static final int INITIAL_EDGES = 1000;
// Recommended maximum scratchpad sizes. The arrays will grow
// larger if needed, but when finished() is called they will be released
// if they have grown larger than these sizes.
public static final int DEFAULT_INDICES_SIZE = 8192;
public static final int DEFAULT_CROSSINGS_SIZE = 32*1024;
// Antialiasing
private int SUBPIXEL_LG_POSITIONS_X;
private int SUBPIXEL_LG_POSITIONS_Y;
private int SUBPIXEL_MASK_X;
private int SUBPIXEL_MASK_Y;
private int SUBPIXEL_POSITIONS_X;
private int SUBPIXEL_POSITIONS_Y;
int MAX_AA_ALPHA;
private int MAX_AA_ALPHA_DENOM;
private int HALF_MAX_AA_ALPHA_DENOM;
private int XSHIFT;
private int YSHIFT;
private int YSTEP;
private int HYSTEP;
private int YMASK;
private static final int MIN_QUAD_OPT_WIDTH = 100 << 16;
import java.util.Arrays;
// Cache to store RLE-encoded coverage mask of the current primitive
PiscesCache cache;
// Bounds of the drawing region, at S15.16 precsion
private int boundsMinX, boundsMinY, boundsMaxX, boundsMaxY;
// Bounds of the current primitive, at subsample precision
private int rasterMinX, rasterMaxX, rasterMinY, rasterMaxY;
public class Renderer implements LineSink {
// Pixel bounding box for current primitive
private int bboxX0, bboxY0, bboxX1, bboxY1;
// Current winding rule
private int windingRule;
///////////////////////////////////////////////////////////////////////////////
// Scan line iterator and edge crossing data.
//////////////////////////////////////////////////////////////////////////////
// Current drawing position, i.e., final point of last segment
private int x0, y0;
// Position of most recent 'moveTo' command
private int sx0, sy0;
private int[] crossings;
// Buffer to be filled with one row's worth of alpha values
private byte[] rowAA; // needs to be short if 16x16 subsampling
// This is an array of indices into the edge array. It is initialized to
// [i * SIZEOF_STRUCT_EDGE for i in range(0, edgesSize/SIZEOF_STRUCT_EDGE)]
// (where range(i, j) is i,i+1,...,j-1 -- just like in python).
// The reason for keeping this is because we need the edges array sorted
// by y0, but we don't want to move all that data around, so instead we
// sort the indices into the edge array, and use edgeIndices to access
// the edges array. This is meant to simulate a pointer array (hence the name)
private int[] edgePtrs;
// Track the number of vertical extrema of the incoming edge list
// in order to determine the maximum number of crossings of a
// scanline
private int firstOrientation;
private int lastOrientation;
private int flips;
// crossing bounds. The bounds are not necessarily tight (the scan line
// at minY, for example, might have no crossings). The x bounds will
// be accumulated as crossings are computed.
private int minY, maxY;
private int minX, maxX;
private int nextY;
// Parameters for emitRow
private int alphaWidth;
// indices into the edge pointer list. They indicate the "active" sublist in
// the edge list (the portion of the list that contains all the edges that
// cross the next scan line).
private int lo, hi;
public Renderer() {
private static final int INIT_CROSSINGS_SIZE = 50;
private void ScanLineItInitialize() {
crossings = new int[INIT_CROSSINGS_SIZE];
edgePtrs = new int[edgesSize / SIZEOF_STRUCT_EDGE];
for (int i = 0; i < edgePtrs.length; i++) {
edgePtrs[i] = i * SIZEOF_STRUCT_EDGE;
}
public void setAntialiasing(int subpixelLgPositionsX,
int subpixelLgPositionsY) {
this.SUBPIXEL_LG_POSITIONS_X = subpixelLgPositionsX;
this.SUBPIXEL_LG_POSITIONS_Y = subpixelLgPositionsY;
qsort(0, edgePtrs.length - 1);
this.SUBPIXEL_MASK_X =
(1 << (SUBPIXEL_LG_POSITIONS_X)) - 1;
this.SUBPIXEL_MASK_Y =
(1 << (SUBPIXEL_LG_POSITIONS_Y)) - 1;
this.SUBPIXEL_POSITIONS_X =
1 << (SUBPIXEL_LG_POSITIONS_X);
this.SUBPIXEL_POSITIONS_Y =
1 << (SUBPIXEL_LG_POSITIONS_Y);
this.MAX_AA_ALPHA =
(SUBPIXEL_POSITIONS_X*SUBPIXEL_POSITIONS_Y);
this.MAX_AA_ALPHA_DENOM = 255*MAX_AA_ALPHA;
this.HALF_MAX_AA_ALPHA_DENOM = MAX_AA_ALPHA_DENOM/2;
this.XSHIFT = 16 - SUBPIXEL_LG_POSITIONS_X;
this.YSHIFT = 16 - SUBPIXEL_LG_POSITIONS_Y;
this.YSTEP = 1 << YSHIFT;
this.HYSTEP = 1 << (YSHIFT - 1);
this.YMASK = ~(YSTEP - 1);
}
// We don't care if we clip some of the line off with ceil, since
// no scan line crossings will be eliminated (in fact, the ceil is
// the y of the first scan line crossing).
nextY = minY = Math.max(boundsMinY, (int)Math.ceil(edgeMinY));
maxY = Math.min(boundsMaxY, (int)Math.ceil(edgeMaxY));
public int getSubpixelLgPositionsX() {
return SUBPIXEL_LG_POSITIONS_X;
for (lo = 0; lo < edgePtrs.length && edges[edgePtrs[lo]+Y1] <= nextY; lo++)
;
for (hi = lo; hi < edgePtrs.length && edges[edgePtrs[hi]+CURY] <= nextY; hi++)
; // the active list is *edgePtrs[lo] (inclusive) *edgePtrs[hi] (exclusive)
for (int i = lo; i < hi; i++) {
setCurY(edgePtrs[i], nextY);
}
public int getSubpixelLgPositionsY() {
return SUBPIXEL_LG_POSITIONS_Y;
// We accumulate X in the iterator because accumulating it in addEdge
// like we do with Y does not do much good: if there's an edge
// (0,0)->(1000,10000), and if y gets clipped to 1000, then the x
// bound should be 100, but the accumulator from addEdge would say 1000,
// so we'd still have to accumulate the X bounds as we add crossings.
minX = boundsMinX;
maxX = boundsMaxX;
}
public void setWindingRule(int windingRule) {
this.windingRule = windingRule;
private int ScanLineItCurrentY() {
return nextY - 1;
}
public int getWindingRule() {
return windingRule;
private int ScanLineItGoToNextYAndComputeCrossings() {
// we go through the active list and remove the ones that don't cross
// the nextY scanline.
int crossingIdx = 0;
for (int i = lo; i < hi; i++) {
if (edges[edgePtrs[i]+Y1] <= nextY) {
edgePtrs[i] = edgePtrs[lo++];
}
public void beginRendering(int boundsX, int boundsY,
int boundsWidth, int boundsHeight) {
lastOrientation = 0;
flips = 0;
resetEdges();
this.boundsMinX = boundsX << 16;
this.boundsMinY = boundsY << 16;
this.boundsMaxX = (boundsX + boundsWidth) << 16;
this.boundsMaxY = (boundsY + boundsHeight) << 16;
this.bboxX0 = boundsX;
this.bboxY0 = boundsY;
this.bboxX1 = boundsX + boundsWidth;
this.bboxY1 = boundsY + boundsHeight;
}
public void moveTo(int x0, int y0) {
// System.out.println("Renderer: moveTo " + x0/65536.0 + " " + y0/65536.0);
close();
this.sx0 = this.x0 = x0;
this.sy0 = this.y0 = y0;
this.lastOrientation = 0;
if (hi - lo > crossings.length) {
int newSize = Math.max(hi - lo, crossings.length * 2);
crossings = Arrays.copyOf(crossings, newSize);
}
public void lineJoin() {
// System.out.println("Renderer: lineJoin");
// do nothing
// Now every edge between lo and hi crosses nextY. Compute it's
// crossing and put it in the crossings array.
for (int i = lo; i < hi; i++) {
addCrossing(nextY, getCurCrossing(edgePtrs[i]), (int)edges[edgePtrs[i]+OR], crossingIdx);
gotoNextY(edgePtrs[i]);
crossingIdx++;
}
public void lineTo(int x1, int y1) {
// System.out.println("Renderer: lineTo " + x1/65536.0 + " " + y1/65536.0);
// Ignore horizontal lines
// Next line will count flip
if (y0 == y1) {
this.x0 = x1;
return;
nextY++;
// Expand active list to include new edges.
for (; hi < edgePtrs.length && edges[edgePtrs[hi]+CURY] <= nextY; hi++) {
setCurY(edgePtrs[hi], nextY);
}
int orientation = (y0 < y1) ? 1 : -1;
if (lastOrientation == 0) {
firstOrientation = orientation;
} else if (orientation != lastOrientation) {
++flips;
Arrays.sort(crossings, 0, crossingIdx);
return crossingIdx;
}
lastOrientation = orientation;
// Bias Y by 1 ULP so endpoints never lie on a scanline
addEdge(x0, y0 | 0x1, x1, y1 | 0x1);
this.x0 = x1;
this.y0 = y1;
private boolean ScanLineItHasNext() {
return nextY < maxY;
}
public void close() {
// System.out.println("Renderer: close");
int orientation = lastOrientation;
if (y0 != sy0) {
orientation = (y0 < sy0) ? 1 : -1;
}
if (orientation != firstOrientation) {
++flips;
private void addCrossing(int y, int x, int or, int idx) {
if (x < minX) {
minX = x;
}
lineTo(sx0, sy0);
if (x > maxX) {
maxX = x;
}
public void end() {
close();
// System.out.println("Renderer: end");
// do nothing
}
// Scan convert a single edge
private void computeCrossingsForEdge(int index,
int boundsMinY, int boundsMaxY) {
int iy0 = edges[index + 1];
int iy1 = edges[index + 3];
// Clip to valid Y range
int clipy0 = (iy0 > boundsMinY) ? iy0 : boundsMinY;
int clipy1 = (iy1 < boundsMaxY) ? iy1 : boundsMaxY;
int minY = ((clipy0 + HYSTEP) & YMASK) + HYSTEP;
int maxY = ((clipy1 - HYSTEP) & YMASK) + HYSTEP;
// IMPL_NOTE - If line falls outside the valid X range, could
// draw a vertical line instead
// Exit if no scanlines are crossed
if (minY > maxY) {
return;
x <<= 1;
crossings[idx] = ((or == 1) ? (x | 0x1) : x);
}
// Scan convert line using a DDA approach
int ix0 = edges[index];
int ix1 = edges[index + 2];
long dx = ((long) ix1) - ix0;
long dy = ((long) iy1) - iy0;
// Compute first crossing point at y = minY
int orientation = edges[index + 4];
int y = minY;
long lx = (((long) y) - iy0)*dx/dy + ix0;
addCrossing(y >> YSHIFT, (int)(lx >> XSHIFT), orientation);
// Advance y to next scanline, exit if past endpoint
y += YSTEP;
if (y > maxY) {
return;
// quicksort implementation for sorting the edge indices ("pointers")
// by increasing y0. first, last are indices into the "pointer" array
// It sorts the pointer array from first (inclusive) to last (inclusive)
private void qsort(int first, int last) {
if (last > first) {
int p = partition(first, last);
if (first < p - 1) {
qsort(first, p - 1);
}
// Compute xstep only if additional scanlines are crossed
// For each scanline, add xstep to lx and YSTEP to y and
// emit the new crossing
long xstep = ((long)YSTEP*dx)/dy;
for (; y <= maxY; y += YSTEP) {
lx += xstep;
addCrossing(y >> YSHIFT, (int)(lx >> XSHIFT), orientation);
if (p < last) {
qsort(p, last);
}
}
private void computeBounds() {
rasterMinX = crossingMinX & ~SUBPIXEL_MASK_X;
rasterMaxX = crossingMaxX | SUBPIXEL_MASK_X;
rasterMinY = crossingMinY & ~SUBPIXEL_MASK_Y;
rasterMaxY = crossingMaxY | SUBPIXEL_MASK_Y;
// If nothing was drawn, we have:
// minX = Integer.MAX_VALUE and maxX = Integer.MIN_VALUE
// so nothing to render
if (rasterMinX > rasterMaxX || rasterMinY > rasterMaxY) {
rasterMinX = 0;
rasterMaxX = -1;
rasterMinY = 0;
rasterMaxY = -1;
return;
}
if (rasterMinX < boundsMinX >> XSHIFT) {
rasterMinX = boundsMinX >> XSHIFT;
}
if (rasterMinY < boundsMinY >> YSHIFT) {
rasterMinY = boundsMinY >> YSHIFT;
// i, j are indices into edgePtrs.
private int partition(int i, int j) {
int pivotVal = edgePtrs[i];
while (i <= j) {
// edges[edgePtrs[i]+1] is equivalent to (*(edgePtrs[i])).y0 in C
while (edges[edgePtrs[i]+CURY] < edges[pivotVal+CURY]) { i++; }
while (edges[edgePtrs[j]+CURY] > edges[pivotVal+CURY]) { j--; }
if (i <= j) {
int tmp = edgePtrs[i];
edgePtrs[i] = edgePtrs[j];
edgePtrs[j] = tmp;
i++;
j--;
}
if (rasterMaxX > boundsMaxX >> XSHIFT) {
rasterMaxX = boundsMaxX >> XSHIFT;
}
if (rasterMaxY > boundsMaxY >> YSHIFT) {
rasterMaxY = boundsMaxY >> YSHIFT;
return i;
}
//============================================================================
//////////////////////////////////////////////////////////////////////////////
// EDGE LIST
//////////////////////////////////////////////////////////////////////////////
private static final int INIT_NUM_EDGES = 1000;
private static final int SIZEOF_STRUCT_EDGE = 5;
// The following array is a poor man's struct array:
// it simulates a struct array by having
// edges[SIZEOF_STRUCT_EDGE * i + j] be the jth field in the ith element
// of an array of edge structs.
private float[] edges;
private int edgesSize; // size of the edge list.
private static final int Y1 = 0;
private static final int SLOPE = 1;
private static final int OR = 2; // the orientation. This can be -1 or 1.
// -1 means up, 1 means down.
private static final int CURY = 3; // j = 5 corresponds to the "current Y".
// Each edge keeps track of the last scanline
// crossing it computed, and this is the y coord of
// that scanline.
private static final int CURX = 4; //the x coord of the current crossing.
// Note that while the array is declared as a float[] not all of it's
// elements should be floats. currentY and Orientation should be ints (or int and
// byte respectively), but they all need to be the same type. This isn't
// really a problem because floats can represent exactly all 23 bit integers,
// which should be more than enough.
// Note, also, that we only need x1 for slope computation, so we don't need
// to store it. x0, y0 don't need to be stored either. They can be put into
// curx, cury, and it's ok if they're lost when curx and cury are changed.
// We take this undeniably ugly and error prone approach (instead of simply
// making an Edge class) for performance reasons. Also, it would probably be nicer
// to have one array for each field, but that would defeat the purpose because
// it would make poor use of the processor cache, since we tend to access
// all the fields for one edge at a time.
private float edgeMinY;
private float edgeMaxY;
private void addEdge(float x0, float y0, float x1, float y1) {
float or = (y0 < y1) ? 1f : -1f; // orientation: 1 = UP; -1 = DOWN
if (or == -1) {
float tmp = y0;
y0 = y1;
y1 = tmp;
tmp = x0;
x0 = x1;
x1 = tmp;
}
// skip edges that don't cross a scanline
if (Math.ceil(y0) >= Math.ceil(y1)) {
return;
}
private int clamp(int x, int min, int max) {
if (x < min) {
return min;
} else if (x > max) {
return max;
}
return x;
int newSize = edgesSize + SIZEOF_STRUCT_EDGE;
if (edges.length < newSize) {
edges = Arrays.copyOf(edges, newSize * 2);
}
edges[edgesSize+CURX] = x0;
edges[edgesSize+CURY] = y0;
edges[edgesSize+Y1] = y1;
edges[edgesSize+SLOPE] = (x1 - x0) / (y1 - y0);
edges[edgesSize+OR] = or;
// the crossing values can't be initialized meaningfully yet. This
// will have to wait until setCurY is called
edgesSize += SIZEOF_STRUCT_EDGE;
private void _endRendering() {
if (flips == 0) {
bboxX0 = bboxY0 = 0;
bboxX1 = bboxY1 = -1;
return;
// Accumulate edgeMinY and edgeMaxY
if (y0 < edgeMinY) { edgeMinY = y0; }
if (y1 > edgeMaxY) { edgeMaxY = y1; }
}
// Special case for filling a single rect with a flat, opaque color
// REMIND: This special case was not originally written to fill a
// cache object and called directly to a Blit - it needs some code
// to fill the cache instead to be useful for this usage...
if (false /* Does not work with cache (yet?) */ &&
edgeIdx == 10 &&
edges[0] == edges[2] &&
edges[1] == edges[6] &&
edges[3] == edges[8] &&
edges[5] == edges[7] &&
Math.abs(edges[0] - edges[5]) > MIN_QUAD_OPT_WIDTH)
{
int x0 = edges[0] >> XSHIFT;
int y0 = edges[1] >> YSHIFT;
int x1 = edges[5] >> XSHIFT;
int y1 = edges[3] >> YSHIFT;
if (x0 > x1) {
int tmp = x0;
x0 = x1;
x1 = tmp;
// As far as the following methods care, this edges extends to infinity.
// They can compute the x intersect of any horizontal line.
// precondition: idx is the index to the start of the desired edge.
// So, if the ith edge is wanted, idx should be SIZEOF_STRUCT_EDGE * i
private void setCurY(int idx, int y) {
// compute the x crossing of edge at idx and horizontal line y
// currentXCrossing = (y - y0)*slope + x0
edges[idx + CURX] = (y - edges[idx + CURY]) * edges[idx + SLOPE] + edges[idx+CURX];
edges[idx + CURY] = (float)y;
}
if (y0 > y1) {
int tmp = y0;
y0 = y1;
y1 = tmp;
private void gotoNextY(int idx) {
edges[idx + CURY] += 1f; // i.e. curY += 1
edges[idx + CURX] += edges[idx + SLOPE]; // i.e. curXCrossing += slope
}
int bMinX = this.boundsMinX >> XSHIFT;
int bMinY = this.boundsMinY >> YSHIFT;
int bMaxX = this.boundsMaxX >> XSHIFT;
int bMaxY = this.boundsMaxY >> YSHIFT;
// Clip to image bounds in supersampled coordinates
x0 = clamp(x0, bMinX, bMaxX);
x1 = clamp(x1, bMinX, bMaxX);
y0 = clamp(y0, bMinY, bMaxY);
y1 = clamp(y1, bMinY, bMaxY);
/*
* REMIND: Need to fill the cache here instead...
Blit.fillRectSrcOver(this,
imageData, imageType,
imageOffset,
imageScanlineStride, imagePixelStride,
width, height,
x0, y0, x1, y1,
cred, cgreen, cblue);
*/
private int getCurCrossing(int idx) {
return (int)edges[idx + CURX];
}
//====================================================================================
bboxX0 = x0 >> SUBPIXEL_LG_POSITIONS_X;
bboxY0 = y0 >> SUBPIXEL_LG_POSITIONS_Y;
bboxX1 = (x1 + SUBPIXEL_POSITIONS_X - 1)
>> SUBPIXEL_LG_POSITIONS_X;
bboxY1 = (y1 + SUBPIXEL_POSITIONS_Y - 1)
>> SUBPIXEL_LG_POSITIONS_Y;
public static final int WIND_EVEN_ODD = 0;
public static final int WIND_NON_ZERO = 1;
return;
}
// Antialiasing
final private int SUBPIXEL_LG_POSITIONS_X;
final private int SUBPIXEL_LG_POSITIONS_Y;
final private int SUBPIXEL_POSITIONS_X;
final private int SUBPIXEL_POSITIONS_Y;
final private int SUBPIXEL_MASK_X;
final private int SUBPIXEL_MASK_Y;
final int MAX_AA_ALPHA;
int minY = (edgeMinY > boundsMinY) ? edgeMinY : boundsMinY;
int maxY = (edgeMaxY < boundsMaxY) ? edgeMaxY : boundsMaxY;
// Cache to store RLE-encoded coverage mask of the current primitive
final PiscesCache cache;
// Check for empty intersection of primitive with the drawing area
if (minY > maxY) {
bboxX0 = bboxY0 = 0;
bboxX1 = bboxY1 = -1;
return;
}
// Bounds of the drawing region, at subpixel precision.
final private int boundsMinX, boundsMinY, boundsMaxX, boundsMaxY;
// Compute Y extent in subpixel coordinates
int iminY = (minY >> YSHIFT) & ~SUBPIXEL_MASK_Y;
int imaxY = (maxY >> YSHIFT) | SUBPIXEL_MASK_Y;
int yextent = (imaxY - iminY) + 1;
// Pixel bounding box for current primitive
private int pix_bboxX0, pix_bboxY0, pix_bboxX1, pix_bboxY1;
// Maximum number of crossings
int size = flips*yextent;
// Current winding rule
final private int windingRule;
int bmax = (boundsMaxY >> YSHIFT) - 1;
if (imaxY > bmax) {
imaxY = bmax;
}
// Current drawing position, i.e., final point of last segment
private float x0, y0;
// Initialize X bounds, will be refined for each strip
bboxX0 = Integer.MAX_VALUE;
bboxX1 = Integer.MIN_VALUE;
// Position of most recent 'moveTo' command
private float pix_sx0, pix_sy0;
// Set Y bounds
bboxY0 = iminY >> SUBPIXEL_LG_POSITIONS_Y;
bboxY1 = (imaxY + SUBPIXEL_POSITIONS_Y - 1) >> SUBPIXEL_LG_POSITIONS_Y;
public Renderer(int subpixelLgPositionsX, int subpixelLgPositionsY,
int pix_boundsX, int pix_boundsY,
int pix_boundsWidth, int pix_boundsHeight,
int windingRule,
PiscesCache cache) {
this.SUBPIXEL_LG_POSITIONS_X = subpixelLgPositionsX;
this.SUBPIXEL_LG_POSITIONS_Y = subpixelLgPositionsY;
this.SUBPIXEL_MASK_X = (1 << (SUBPIXEL_LG_POSITIONS_X)) - 1;
this.SUBPIXEL_MASK_Y = (1 << (SUBPIXEL_LG_POSITIONS_Y)) - 1;
this.SUBPIXEL_POSITIONS_X = 1 << (SUBPIXEL_LG_POSITIONS_X);
this.SUBPIXEL_POSITIONS_Y = 1 << (SUBPIXEL_LG_POSITIONS_Y);
this.MAX_AA_ALPHA = (SUBPIXEL_POSITIONS_X * SUBPIXEL_POSITIONS_Y);
// Compute number of rows that can be processing using
// a crossings table no larger than DEFAULT_CROSSINGS_SIZE.
// However, we must process at least one row, so we grow the table
// temporarily if needed. This would require an object with a
// huge number of flips.
int rows = DEFAULT_CROSSINGS_SIZE/(flips*SUBPIXEL_POSITIONS_Y);
rows = Math.min(rows, yextent);
rows = Math.max(rows, 1);
for (int i = iminY; i <= imaxY; i += rows*SUBPIXEL_POSITIONS_Y) {
// Compute index of last scanline to be processed in this pass
int last = Math.min(i + rows*SUBPIXEL_POSITIONS_Y - 1, imaxY);
setCrossingsExtents(i, last, flips);
this.edges = new float[SIZEOF_STRUCT_EDGE * INIT_NUM_EDGES];
edgeMinY = Float.POSITIVE_INFINITY;
edgeMaxY = Float.NEGATIVE_INFINITY;
edgesSize = 0;
int bminY = i << YSHIFT;
int bmaxY = (last << YSHIFT) | ~YMASK;
this.windingRule = windingRule;
this.cache = cache;
// Process edges from the edge list
int maxIdx = edgeIdx;
for (int index = 0; index < maxIdx; index += 5) {
// Test y1 < min:
//
// If edge lies entirely above current strip,
// discard it
if (edges[index + 3] < bminY) {
// Overwrite the edge with the last edge
edgeIdx -= 5;
int fidx = edgeIdx;
int tidx = index;
edges[tidx++] = edges[fidx++];
edges[tidx++] = edges[fidx++];
edges[tidx++] = edges[fidx++];
edges[tidx++] = edges[fidx++];
edges[tidx ] = edges[fidx ];
this.boundsMinX = pix_boundsX * SUBPIXEL_POSITIONS_X;
this.boundsMinY = pix_boundsY * SUBPIXEL_POSITIONS_Y;
this.boundsMaxX = (pix_boundsX + pix_boundsWidth) * SUBPIXEL_POSITIONS_X;
this.boundsMaxY = (pix_boundsY + pix_boundsHeight) * SUBPIXEL_POSITIONS_Y;
maxIdx -= 5;
index -= 5;
continue;
this.pix_bboxX0 = pix_boundsX;
this.pix_bboxY0 = pix_boundsY;
this.pix_bboxX1 = pix_boundsX + pix_boundsWidth;
this.pix_bboxY1 = pix_boundsY + pix_boundsHeight;
}
// Test y0 > max:
//
// If edge lies entirely below current strip,
// skip it for now
if (edges[index + 1] > bmaxY) {
continue;
private float tosubpixx(float pix_x) {
return pix_x * SUBPIXEL_POSITIONS_X;
}
computeCrossingsForEdge(index, bminY, bmaxY);
private float tosubpixy(float pix_y) {
return pix_y * SUBPIXEL_POSITIONS_Y;
}
computeBounds();
if (rasterMaxX < rasterMinX) {
continue;
public void moveTo(float pix_x0, float pix_y0) {
close();
this.pix_sx0 = pix_x0;
this.pix_sy0 = pix_y0;
this.y0 = tosubpixy(pix_y0);
this.x0 = tosubpixx(pix_x0);
}
bboxX0 = Math.min(bboxX0,
rasterMinX >> SUBPIXEL_LG_POSITIONS_X);
bboxX1 = Math.max(bboxX1,
(rasterMaxX + SUBPIXEL_POSITIONS_X - 1)
>> SUBPIXEL_LG_POSITIONS_X);
renderStrip();
}
public void lineJoin() { /* do nothing */ }
// Free up any unusually large scratchpad memory used by the
// preceding primitive
crossingListFinished();
}
public void lineTo(float pix_x1, float pix_y1) {
float x1 = tosubpixx(pix_x1);
float y1 = tosubpixy(pix_y1);
public void endRendering() {
// Set up the cache to accumulate the bounding box
if (cache != null) {
cache.bboxX0 = Integer.MAX_VALUE;
cache.bboxY0 = Integer.MAX_VALUE;
cache.bboxX1 = Integer.MIN_VALUE;
cache.bboxY1 = Integer.MIN_VALUE;
// Ignore horizontal lines
if (y0 == y1) {
this.x0 = x1;
return;
}
_endRendering();
}
addEdge(x0, y0, x1, y1);
public void getBoundingBox(int[] bbox) {
bbox[0] = bboxX0;
bbox[1] = bboxY0;
bbox[2] = bboxX1 - bboxX0;
bbox[3] = bboxY1 - bboxY0;
this.x0 = x1;
this.y0 = y1;
}
private void renderStrip() {
// Grow rowAA according to the raster width
int width = (rasterMaxX - rasterMinX + 1) >> SUBPIXEL_LG_POSITIONS_X;
alphaWidth = width;
public void close() {
// lineTo expects its input in pixel coordinates.
lineTo(pix_sx0, pix_sy0);
}
// Allocate one extra entry in rowAA to avoid a conditional in
// the rendering loop
int bufLen = width + 1;
if (this.rowAA == null || this.rowAA.length < bufLen) {
this.rowAA = new byte[bufLen];
public void end() {
close();
}
private void _endRendering() {
// Mask to determine the relevant bit of the crossing sum
// 0x1 if EVEN_ODD, all bits if NON_ZERO
int mask = (windingRule == WIND_EVEN_ODD) ? 0x1 : ~0x0;
int y = 0;
int prevY = rasterMinY - 1;
int minX = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE;
// add 1 to better deal with the last pixel in a pixel row.
int width = ((boundsMaxX - boundsMinX) >> SUBPIXEL_LG_POSITIONS_X) + 1;
byte[] alpha = new byte[width+1];
iterateCrossings();
while (hasMoreCrossingRows()) {
y = crossingY;
// Now we iterate through the scanlines. We must tell emitRow the coord
// of the first non-transparent pixel, so we must keep accumulators for
// the first and last pixels of the section of the current pixel row
// that we will emit.
// We also need to accumulate pix_bbox*, but the iterator does it
// for us. We will just get the values from it once this loop is done
int pix_maxX = Integer.MIN_VALUE;
int pix_minX = Integer.MAX_VALUE;
// Emit any skipped rows
for (int j = prevY + 1; j < y; j++) {
if (((j & SUBPIXEL_MASK_Y) == SUBPIXEL_MASK_Y) ||
(j == rasterMaxY)) {
emitRow(j >> SUBPIXEL_LG_POSITIONS_Y, 0, -1);
}
}
prevY = y;
int y = boundsMinY; // needs to be declared here so we emit the last row properly.
ScanLineItInitialize();
for ( ; ScanLineItHasNext(); ) {
int numCrossings = ScanLineItGoToNextYAndComputeCrossings();
y = ScanLineItCurrentY();
if (crossingRowIndex < crossingRowCount) {
int lx = crossings[crossingRowOffset + crossingRowIndex];
lx >>= 1;
int hx = crossings[crossingRowOffset + crossingRowCount - 1];
hx >>= 1;
int x0 = lx > rasterMinX ? lx : rasterMinX;
int x1 = hx < rasterMaxX ? hx : rasterMaxX;
x0 -= rasterMinX;
x1 -= rasterMinX;
if (numCrossings > 0) {
int lowx = crossings[0] >> 1;
int highx = crossings[numCrossings - 1] >> 1;
int x0 = Math.max(lowx, boundsMinX);
int x1 = Math.min(highx, boundsMaxX);
minX = Math.min(minX, x0 >> SUBPIXEL_LG_POSITIONS_X);
maxX = Math.max(maxX, x1 >> SUBPIXEL_LG_POSITIONS_X);
pix_minX = Math.min(pix_minX, x0 >> SUBPIXEL_LG_POSITIONS_X);
pix_maxX = Math.max(pix_maxX, x1 >> SUBPIXEL_LG_POSITIONS_X);
}
int sum = 0;
int prev = rasterMinX;
while (crossingRowIndex < crossingRowCount) {
int crxo = crossings[crossingRowOffset + crossingRowIndex];
crossingRowIndex++;
int prev = boundsMinX;
for (int i = 0; i < numCrossings; i++) {
int curxo = crossings[i];
int curx = curxo >> 1;
int crorientation = ((curxo & 0x1) == 0x1) ? 1 : -1;
if ((sum & mask) != 0) {
int x0 = Math.max(prev, boundsMinX);
int x1 = Math.min(curx, boundsMaxX);
if (x0 < x1) {
x0 -= boundsMinX; // turn x0, x1 from coords to indeces
x1 -= boundsMinX; // in the alpha array.
int crx = crxo >> 1;
int crorientation = ((crxo & 0x1) == 0x1) ? 1 : -1;
int pix_x = x0 >> SUBPIXEL_LG_POSITIONS_X;
int pix_xmaxm1 = (x1 - 1) >> SUBPIXEL_LG_POSITIONS_X;
if ((sum & mask) != 0) {
// Clip to active X range, if x1 < x0 loop will
// have no effect
int x0 = prev > rasterMinX ? prev : rasterMinX;
int x1 = crx < rasterMaxX ? crx : rasterMaxX;
// Empty spans
if (x1 > x0) {
x0 -= rasterMinX;
x1 -= rasterMinX;
// Accumulate alpha, equivalent to:
// for (int x = x0; x < x1; x++) {
// ++rowAA[x >> SUBPIXEL_LG_POSITIONS_X];
// }
//
// In the middle of the span, we can update a full
// pixel at a time (i.e., SUBPIXEL_POSITIONS_X
// subpixels)
int x = x0 >> SUBPIXEL_LG_POSITIONS_X;
int xmaxm1 = (x1 - 1) >> SUBPIXEL_LG_POSITIONS_X;
if (x == xmaxm1) {
if (pix_x == pix_xmaxm1) {
// Start and end in same pixel
rowAA[x] += x1 - x0;
alpha[pix_x] += (x1 - x0);
alpha[pix_x+1] -= (x1 - x0);
} else {
// Start and end in different pixels
rowAA[x++] += SUBPIXEL_POSITIONS_X -
(x0 & SUBPIXEL_MASK_X);
int xmax = x1 >> SUBPIXEL_LG_POSITIONS_X;
while (x < xmax) {
rowAA[x++] += SUBPIXEL_POSITIONS_X;
}
// Note - at this point it is possible that
// x == width, which implies that
// x1 & SUBPIXEL_MASK_X == 0. We allocate
// one extra entry in rowAA so this
// assignment will be harmless. The alternative
// is an extra conditional here, or some other
// scheme to deal with the last pixel better.
rowAA[x] += x1 & SUBPIXEL_MASK_X;
int pix_xmax = x1 >> SUBPIXEL_LG_POSITIONS_X;
alpha[pix_x] += SUBPIXEL_POSITIONS_X - (x0 & SUBPIXEL_MASK_X);
alpha[pix_x+1] += (x0 & SUBPIXEL_MASK_X);
alpha[pix_xmax] -= SUBPIXEL_POSITIONS_X - (x1 & SUBPIXEL_MASK_X);
alpha[pix_xmax+1] -= (x1 & SUBPIXEL_MASK_X);
}
}
}
sum += crorientation;
prev = crx;
prev = curx;
}
// Every SUBPIXEL_POSITIONS rows, output an antialiased row
if (((y & SUBPIXEL_MASK_Y) == SUBPIXEL_MASK_Y) ||
(y == rasterMaxY)) {
emitRow(y >> SUBPIXEL_LG_POSITIONS_Y, minX, maxX);
minX = Integer.MAX_VALUE;
maxX = Integer.MIN_VALUE;
if ((y & SUBPIXEL_MASK_Y) == SUBPIXEL_MASK_Y) {
emitRow(alpha, y >> SUBPIXEL_LG_POSITIONS_Y, pix_minX, pix_maxX);
pix_minX = Integer.MAX_VALUE;
pix_maxX = Integer.MIN_VALUE;
}
}
// Emit final row
for (int j = prevY + 1; j <= rasterMaxY; j++) {
if (((j & SUBPIXEL_MASK_Y) == SUBPIXEL_MASK_Y) ||
(j == rasterMaxY)) {
emitRow(j >> SUBPIXEL_LG_POSITIONS_Y, minX, maxX);
minX = Integer.MAX_VALUE;
maxX = Integer.MIN_VALUE;
}
if (pix_maxX >= pix_minX) {
emitRow(alpha, y >> SUBPIXEL_LG_POSITIONS_Y, pix_minX, pix_maxX);
}
pix_bboxX0 = minX >> SUBPIXEL_LG_POSITIONS_X;
pix_bboxX1 = maxX >> SUBPIXEL_LG_POSITIONS_X;
pix_bboxY0 = minY >> SUBPIXEL_LG_POSITIONS_Y;
pix_bboxY1 = maxY >> SUBPIXEL_LG_POSITIONS_Y;
}
private void clearAlpha(byte[] alpha,
int width,
int minX, int maxX) {
if (maxX >= minX) {
int w = maxX - minX + 1;
if (w + minX > width) {
w = width - minX;
}
int aidx = minX;
for (int i = 0; i < w; i++, aidx++) {
alpha[aidx] = (byte)0;
public void endRendering() {
// Set up the cache to accumulate the bounding box
if (cache != null) {
cache.bboxX0 = Integer.MAX_VALUE;
cache.bboxY0 = Integer.MAX_VALUE;
cache.bboxX1 = Integer.MIN_VALUE;
cache.bboxY1 = Integer.MIN_VALUE;
}
_endRendering();
}
public void getBoundingBox(int[] pix_bbox) {
pix_bbox[0] = pix_bboxX0;
pix_bbox[1] = pix_bboxY0;
pix_bbox[2] = pix_bboxX1 - pix_bboxX0;
pix_bbox[3] = pix_bboxY1 - pix_bboxY0;
}
private void emitRow(int y, int minX, int maxX) {
private void emitRow(byte[] alphaRow, int pix_y, int pix_from, int pix_to) {
// Copy rowAA data into the cache if one is present
if (cache != null) {
if (maxX >= minX) {
int x0 = minX + (rasterMinX >> SUBPIXEL_LG_POSITIONS_X);
int x1 = maxX + (rasterMinX >> SUBPIXEL_LG_POSITIONS_X);
if (pix_to >= pix_from) {
cache.startRow(pix_y, pix_from, pix_to);
cache.startRow(y, x0, x1);
int srcIdx = minX;
// Perform run-length encoding and store results in the cache
int from = pix_from - (boundsMinX >> SUBPIXEL_LG_POSITIONS_X);
int to = pix_to - (boundsMinX >> SUBPIXEL_LG_POSITIONS_X);
// Perform run-length encoding
// and store results in the cache
byte startVal = rowAA[srcIdx++];
int runLen = 1;
while (srcIdx <= maxX) {
byte nextVal = rowAA[srcIdx++];
byte startVal = alphaRow[from];
for (int i = from + 1; i <= to; i++) {
byte nextVal = (byte)(startVal + alphaRow[i]);
if (nextVal == startVal && runLen < 255) {
++runLen;
runLen++;
} else {
cache.addRLERun(startVal, runLen);
runLen = 1;
startVal = nextVal;
}
......@@ -656,190 +505,6 @@ public class Renderer extends LineSink {
cache.addRLERun((byte)0, 0);
}
}
clearAlpha(rowAA,
alphaWidth,
minX, maxX);
}
public void setCache(PiscesCache cache) {
this.cache = cache;
}
// Edge list data
private int[] edges = new int[5*INITIAL_EDGES];
private int edgeIdx = 0;
private int edgeMinY = Integer.MAX_VALUE;
private int edgeMaxY = Integer.MIN_VALUE;
private void addEdge(int x0, int y0, int x1, int y1) {
int newLen = edgeIdx + 5;
if (edges.length < newLen) {
int[] tmp = new int[Math.max(11*edges.length/10, newLen)];
System.arraycopy(edges, 0, tmp, 0, edgeIdx);
this.edges = tmp;
}
int orientation = 1;
if (y0 > y1) {
int tmp = y0;
y0 = y1;
y1 = tmp;
orientation = -1;
}
// Skip edges that don't cross a subsampled scanline
int eminY = ((y0 + HYSTEP) & YMASK);
int emaxY = ((y1 - HYSTEP) & YMASK);
if (eminY > emaxY) {
return;
}
if (orientation == -1) {
int tmp = x0;
x0 = x1;
x1 = tmp;
}
edges[edgeIdx++] = x0;
edges[edgeIdx++] = y0;
edges[edgeIdx++] = x1;
edges[edgeIdx++] = y1;
edges[edgeIdx++] = orientation;
// Update Y bounds of primitive
if (y0 < edgeMinY) {
edgeMinY = y0;
}
if (y1 > edgeMaxY) {
edgeMaxY = y1;
}
}
private void resetEdges() {
this.edgeIdx = 0;
this.edgeMinY = Integer.MAX_VALUE;
this.edgeMaxY = Integer.MIN_VALUE;
}
// Crossing list data
private int[] crossingIndices;
private int[] crossings;
private int crossingMinY;
private int crossingMaxY;
private int crossingMinX = Integer.MAX_VALUE;
private int crossingMaxX = Integer.MIN_VALUE;
private int crossingMaxXEntries;
private int numCrossings = 0;
private boolean crossingsSorted = false;
private int crossingY;
private int crossingRowCount;
private int crossingRowOffset;
private int crossingRowIndex;
private void setCrossingsExtents(int minY, int maxY, int maxXEntries) {
int yextent = maxY - minY + 1;
// Grow indices array as needed
if (crossingIndices == null || crossingIndices.length < yextent) {
this.crossingIndices =
new int[Math.max(yextent, DEFAULT_INDICES_SIZE)];
}
// Grow crossings array as needed
if (crossings == null || crossings.length < yextent*maxXEntries) {
this.crossings = new int[Math.max(yextent*maxXEntries,
DEFAULT_CROSSINGS_SIZE)];
}
this.crossingMinY = minY;
this.crossingMaxY = maxY;
this.crossingMaxXEntries = maxXEntries;
resetCrossings();
}
private void resetCrossings() {
int yextent = crossingMaxY - crossingMinY + 1;
int start = 0;
for (int i = 0; i < yextent; i++) {
crossingIndices[i] = start;
start += crossingMaxXEntries;
}
crossingMinX = Integer.MAX_VALUE;
crossingMaxX = Integer.MIN_VALUE;
numCrossings = 0;
crossingsSorted = false;
}
// Free sorting arrays if larger than maximum size
private void crossingListFinished() {
if (crossings != null && crossings.length > DEFAULT_CROSSINGS_SIZE) {
crossings = new int[DEFAULT_CROSSINGS_SIZE];
}
if (crossingIndices != null &&
crossingIndices.length > DEFAULT_INDICES_SIZE)
{
crossingIndices = new int[DEFAULT_INDICES_SIZE];
}
}
private void sortCrossings(int[] x, int off, int len) {
for (int i = off + 1; i < off + len; i++) {
int j = i;
int xj = x[j];
int xjm1;
while (j > off && (xjm1 = x[j - 1]) > xj) {
x[j] = xjm1;
x[j - 1] = xj;
j--;
}
}
}
private void sortCrossings() {
int start = 0;
for (int i = 0; i <= crossingMaxY - crossingMinY; i++) {
sortCrossings(crossings, start, crossingIndices[i] - start);
start += crossingMaxXEntries;
}
}
private void addCrossing(int y, int x, int orientation) {
if (x < crossingMinX) {
crossingMinX = x;
}
if (x > crossingMaxX) {
crossingMaxX = x;
}
int index = crossingIndices[y - crossingMinY]++;
x <<= 1;
crossings[index] = (orientation == 1) ? (x | 0x1) : x;
++numCrossings;
}
private void iterateCrossings() {
if (!crossingsSorted) {
sortCrossings();
crossingsSorted = true;
}
crossingY = crossingMinY - 1;
crossingRowOffset = -crossingMaxXEntries;
}
private boolean hasMoreCrossingRows() {
if (++crossingY <= crossingMaxY) {
crossingRowOffset += crossingMaxXEntries;
int y = crossingY - crossingMinY;
crossingRowCount = crossingIndices[y] - y*crossingMaxXEntries;
crossingRowIndex = 0;
return true;
} else {
return false;
}
java.util.Arrays.fill(alphaRow, (byte)0);
}
}
......@@ -25,7 +25,7 @@
package sun.java2d.pisces;
public class Stroker extends LineSink {
public class Stroker implements LineSink {
private static final int MOVE_TO = 0;
private static final int LINE_TO = 1;
......@@ -61,19 +61,15 @@ public class Stroker extends LineSink {
*/
public static final int CAP_SQUARE = 2;
LineSink output;
private final LineSink output;
int lineWidth;
int capStyle;
int joinStyle;
int miterLimit;
private final int capStyle;
private final int joinStyle;
Transform4 transform;
int m00, m01;
int m10, m11;
private final float m00, m01, m10, m11, det;
int lineWidth2;
long scaledLineWidth2;
private final float lineWidth2;
private final float scaledLineWidth2;
// For any pen offset (pen_dx, pen_dy) that does not depend on
// the line orientation, the pen should be transformed so that:
......@@ -88,143 +84,86 @@ public class Stroker extends LineSink {
//
// pen_dx'(r, theta) = r*(m00*cos(theta) + m01*sin(theta))
// pen_dy'(r, theta) = r*(m10*cos(theta) + m11*sin(theta))
int numPenSegments;
int[] pen_dx;
int[] pen_dy;
boolean[] penIncluded;
int[] join;
int[] offset = new int[2];
int[] reverse = new int[100];
int[] miter = new int[2];
long miterLimitSq;
int prev;
int rindex;
boolean started;
boolean lineToOrigin;
boolean joinToOrigin;
int sx0, sy0, sx1, sy1, x0, y0, x1, y1;
int mx0, my0, mx1, my1, omx, omy;
int lx0, ly0, lx1, ly1, lx0p, ly0p, px0, py0;
double m00_2_m01_2;
double m10_2_m11_2;
double m00_m10_m01_m11;
/**
* Empty constructor. <code>setOutput</code> and
* <code>setParameters</code> must be called prior to calling any
* other methods.
*/
public Stroker() {}
private int numPenSegments;
private final float[] pen_dx;
private final float[] pen_dy;
private boolean[] penIncluded;
private final float[] join;
private final float[] offset = new float[2];
private float[] reverse = new float[100];
private final float[] miter = new float[2];
private final float miterLimitSq;
private int prev;
private int rindex;
private boolean started;
private boolean lineToOrigin;
private boolean joinToOrigin;
private float sx0, sy0, sx1, sy1, x0, y0, px0, py0;
private float mx0, my0, omx, omy;
private float m00_2_m01_2;
private float m10_2_m11_2;
private float m00_m10_m01_m11;
/**
* Constructs a <code>Stroker</code>.
*
* @param output an output <code>LineSink</code>.
* @param lineWidth the desired line width in pixels, in S15.16
* format.
* @param lineWidth the desired line width in pixels
* @param capStyle the desired end cap style, one of
* <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
* <code>CAP_SQUARE</code>.
* @param joinStyle the desired line join style, one of
* <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
* <code>JOIN_BEVEL</code>.
* @param miterLimit the desired miter limit, in S15.16 format.
* @param miterLimit the desired miter limit
* @param transform a <code>Transform4</code> object indicating
* the transform that has been previously applied to all incoming
* coordinates. This is required in order to produce consistently
* shaped end caps and joins.
*/
public Stroker(LineSink output,
int lineWidth,
float lineWidth,
int capStyle,
int joinStyle,
int miterLimit,
Transform4 transform) {
setOutput(output);
setParameters(lineWidth, capStyle, joinStyle, miterLimit, transform);
}
/**
* Sets the output <code>LineSink</code> of this
* <code>Stroker</code>.
*
* @param output an output <code>LineSink</code>.
*/
public void setOutput(LineSink output) {
float miterLimit,
float m00, float m01, float m10, float m11) {
this.output = output;
}
/**
* Sets the parameters of this <code>Stroker</code>.
* @param lineWidth the desired line width in pixels, in S15.16
* format.
* @param capStyle the desired end cap style, one of
* <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
* <code>CAP_SQUARE</code>.
* @param joinStyle the desired line join style, one of
* <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
* <code>JOIN_BEVEL</code>.
* @param miterLimit the desired miter limit, in S15.16 format.
* @param transform a <code>Transform4</code> object indicating
* the transform that has been previously applied to all incoming
* coordinates. This is required in order to produce consistently
* shaped end caps and joins.
*/
public void setParameters(int lineWidth,
int capStyle,
int joinStyle,
int miterLimit,
Transform4 transform) {
this.lineWidth = lineWidth;
this.lineWidth2 = lineWidth >> 1;
this.scaledLineWidth2 = ((long)transform.m00*lineWidth2) >> 16;
this.lineWidth2 = lineWidth / 2;
this.scaledLineWidth2 = m00 * lineWidth2;
this.capStyle = capStyle;
this.joinStyle = joinStyle;
this.miterLimit = miterLimit;
this.transform = transform;
this.m00 = transform.m00;
this.m01 = transform.m01;
this.m10 = transform.m10;
this.m11 = transform.m11;
m00_2_m01_2 = m00*m00 + m01*m01;
m10_2_m11_2 = m10*m10 + m11*m11;
m00_m10_m01_m11 = m00*m10 + m01*m11;
this.m00_2_m01_2 = (double)m00*m00 + (double)m01*m01;
this.m10_2_m11_2 = (double)m10*m10 + (double)m11*m11;
this.m00_m10_m01_m11 = (double)m00*m10 + (double)m01*m11;
this.m00 = m00;
this.m01 = m01;
this.m10 = m10;
this.m11 = m11;
det = m00*m11 - m01*m10;
double dm00 = m00/65536.0;
double dm01 = m01/65536.0;
double dm10 = m10/65536.0;
double dm11 = m11/65536.0;
double determinant = dm00*dm11 - dm01*dm10;
float limit = miterLimit * lineWidth2 * det;
this.miterLimitSq = limit*limit;
if (joinStyle == JOIN_MITER) {
double limit =
(miterLimit/65536.0)*(lineWidth2/65536.0)*determinant;
double limitSq = limit*limit;
this.miterLimitSq = (long)(limitSq*65536.0*65536.0);
}
this.numPenSegments = (int)(3.14159f*lineWidth/65536.0f);
if (pen_dx == null || pen_dx.length < numPenSegments) {
this.pen_dx = new int[numPenSegments];
this.pen_dy = new int[numPenSegments];
this.numPenSegments = (int)(3.14159f * lineWidth);
this.pen_dx = new float[numPenSegments];
this.pen_dy = new float[numPenSegments];
this.penIncluded = new boolean[numPenSegments];
this.join = new int[2*numPenSegments];
}
this.join = new float[2*numPenSegments];
for (int i = 0; i < numPenSegments; i++) {
double r = lineWidth/2.0;
double theta = (double)i*2.0*Math.PI/numPenSegments;
double theta = (i * 2.0 * Math.PI)/numPenSegments;
double cos = Math.cos(theta);
double sin = Math.sin(theta);
pen_dx[i] = (int)(r*(dm00*cos + dm01*sin));
pen_dy[i] = (int)(r*(dm10*cos + dm11*sin));
pen_dx[i] = (float)(lineWidth2 * (m00*cos + m01*sin));
pen_dy[i] = (float)(lineWidth2 * (m10*cos + m11*sin));
}
prev = CLOSE;
......@@ -233,32 +172,31 @@ public class Stroker extends LineSink {
lineToOrigin = false;
}
private void computeOffset(int x0, int y0, int x1, int y1, int[] m) {
long lx = (long)x1 - (long)x0;
long ly = (long)y1 - (long)y0;
private void computeOffset(float x0, float y0,
float x1, float y1, float[] m) {
float lx = x1 - x0;
float ly = y1 - y0;
int dx, dy;
float dx, dy;
if (m00 > 0 && m00 == m11 && m01 == 0 & m10 == 0) {
long ilen = PiscesMath.hypot(lx, ly);
float ilen = (float)Math.hypot(lx, ly);
if (ilen == 0) {
dx = dy = 0;
} else {
dx = (int)( (ly*scaledLineWidth2)/ilen);
dy = (int)(-(lx*scaledLineWidth2)/ilen);
dx = (ly * scaledLineWidth2)/ilen;
dy = -(lx * scaledLineWidth2)/ilen;
}
} else {
double dlx = x1 - x0;
double dly = y1 - y0;
double det = (double)m00*m11 - (double)m01*m10;
int sdet = (det > 0) ? 1 : -1;
double a = dly*m00 - dlx*m10;
double b = dly*m01 - dlx*m11;
double dh = PiscesMath.hypot(a, b);
double div = sdet*lineWidth2/(65536.0*dh);
double ddx = dly*m00_2_m01_2 - dlx*m00_m10_m01_m11;
double ddy = dly*m00_m10_m01_m11 - dlx*m10_2_m11_2;
dx = (int)(ddx*div);
dy = (int)(ddy*div);
float a = ly * m00 - lx * m10;
float b = ly * m01 - lx * m11;
float dh = (float)Math.hypot(a, b);
float div = sdet * lineWidth2/dh;
float ddx = ly * m00_2_m01_2 - lx * m00_m10_m01_m11;
float ddy = ly * m00_m10_m01_m11 - lx * m10_2_m11_2;
dx = ddx*div;
dy = ddy*div;
}
m[0] = dx;
......@@ -267,58 +205,43 @@ public class Stroker extends LineSink {
private void ensureCapacity(int newrindex) {
if (reverse.length < newrindex) {
int[] tmp = new int[Math.max(newrindex, 6*reverse.length/5)];
System.arraycopy(reverse, 0, tmp, 0, rindex);
this.reverse = tmp;
reverse = java.util.Arrays.copyOf(reverse, 6*reverse.length/5);
}
}
private boolean isCCW(int x0, int y0,
int x1, int y1,
int x2, int y2) {
int dx0 = x1 - x0;
int dy0 = y1 - y0;
int dx1 = x2 - x1;
int dy1 = y2 - y1;
return (long)dx0*dy1 < (long)dy0*dx1;
private boolean isCCW(float x0, float y0,
float x1, float y1,
float x2, float y2) {
return (x1 - x0) * (y2 - y1) < (y1 - y0) * (x2 - x1);
}
private boolean side(int x, int y, int x0, int y0, int x1, int y1) {
long lx = x;
long ly = y;
long lx0 = x0;
long ly0 = y0;
long lx1 = x1;
long ly1 = y1;
return (ly0 - ly1)*lx + (lx1 - lx0)*ly + (lx0*ly1 - lx1*ly0) > 0;
private boolean side(float x, float y,
float x0, float y0,
float x1, float y1) {
return (y0 - y1)*x + (x1 - x0)*y + (x0*y1 - x1*y0) > 0;
}
private int computeRoundJoin(int cx, int cy,
int xa, int ya,
int xb, int yb,
private int computeRoundJoin(float cx, float cy,
float xa, float ya,
float xb, float yb,
int side,
boolean flip,
int[] join) {
int px, py;
float[] join) {
float px, py;
int ncoords = 0;
boolean centerSide;
if (side == 0) {
centerSide = side(cx, cy, xa, ya, xb, yb);
} else {
centerSide = (side == 1) ? true : false;
centerSide = (side == 1);
}
for (int i = 0; i < numPenSegments; i++) {
px = cx + pen_dx[i];
py = cy + pen_dy[i];
boolean penSide = side(px, py, xa, ya, xb, yb);
if (penSide != centerSide) {
penIncluded[i] = true;
} else {
penIncluded[i] = false;
}
penIncluded[i] = (penSide != centerSide);
}
int start = -1, end = -1;
......@@ -338,10 +261,10 @@ public class Stroker extends LineSink {
}
if (start != -1 && end != -1) {
long dxa = cx + pen_dx[start] - xa;
long dya = cy + pen_dy[start] - ya;
long dxb = cx + pen_dx[start] - xb;
long dyb = cy + pen_dy[start] - yb;
float dxa = cx + pen_dx[start] - xa;
float dya = cy + pen_dy[start] - ya;
float dxb = cx + pen_dx[start] - xb;
float dyb = cy + pen_dy[start] - yb;
boolean rev = (dxa*dxa + dya*dya > dxb*dxb + dyb*dyb);
int i = rev ? end : start;
......@@ -362,22 +285,25 @@ public class Stroker extends LineSink {
return ncoords/2;
}
private static final long ROUND_JOIN_THRESHOLD = 1000L;
private static final long ROUND_JOIN_INTERNAL_THRESHOLD = 1000000000L;
// pisces used to use fixed point arithmetic with 16 decimal digits. I
// didn't want to change the values of the constants below when I converted
// it to floating point, so that's why the divisions by 2^16 are there.
private static final float ROUND_JOIN_THRESHOLD = 1000/65536f;
private static final float ROUND_JOIN_INTERNAL_THRESHOLD = 1000000000/65536f;
private void drawRoundJoin(int x, int y,
int omx, int omy, int mx, int my,
private void drawRoundJoin(float x, float y,
float omx, float omy, float mx, float my,
int side,
boolean flip,
boolean rev,
long threshold) {
float threshold) {
if ((omx == 0 && omy == 0) || (mx == 0 && my == 0)) {
return;
}
long domx = (long)omx - mx;
long domy = (long)omy - my;
long len = domx*domx + domy*domy;
float domx = omx - mx;
float domy = omy - my;
float len = domx*domx + domy*domy;
if (len < threshold) {
return;
}
......@@ -389,10 +315,10 @@ public class Stroker extends LineSink {
my = -my;
}
int bx0 = x + omx;
int by0 = y + omy;
int bx1 = x + mx;
int by1 = y + my;
float bx0 = x + omx;
float by0 = y + omy;
float bx1 = x + mx;
float by1 = y + my;
int npoints = computeRoundJoin(x, y,
bx0, by0, bx1, by1, side, flip,
......@@ -404,40 +330,30 @@ public class Stroker extends LineSink {
// Return the intersection point of the lines (ix0, iy0) -> (ix1, iy1)
// and (ix0p, iy0p) -> (ix1p, iy1p) in m[0] and m[1]
private void computeMiter(int ix0, int iy0, int ix1, int iy1,
int ix0p, int iy0p, int ix1p, int iy1p,
int[] m) {
long x0 = ix0;
long y0 = iy0;
long x1 = ix1;
long y1 = iy1;
long x0p = ix0p;
long y0p = iy0p;
long x1p = ix1p;
long y1p = iy1p;
long x10 = x1 - x0;
long y10 = y1 - y0;
long x10p = x1p - x0p;
long y10p = y1p - y0p;
long den = (x10*y10p - x10p*y10) >> 16;
private void computeMiter(float x0, float y0, float x1, float y1,
float x0p, float y0p, float x1p, float y1p,
float[] m) {
float x10 = x1 - x0;
float y10 = y1 - y0;
float x10p = x1p - x0p;
float y10p = y1p - y0p;
float den = x10*y10p - x10p*y10;
if (den == 0) {
m[0] = ix0;
m[1] = iy0;
m[0] = x0;
m[1] = y0;
return;
}
long t = (x1p*(y0 - y0p) - x0*y10p + x0p*(y1p - y0)) >> 16;
m[0] = (int)(x0 + (t*x10)/den);
m[1] = (int)(y0 + (t*y10)/den);
float t = x1p*(y0 - y0p) - x0*y10p + x0p*(y1p - y0);
m[0] = x0 + (t*x10)/den;
m[1] = y0 + (t*y10)/den;
}
private void drawMiter(int px0, int py0,
int x0, int y0,
int x1, int y1,
int omx, int omy, int mx, int my,
private void drawMiter(float px0, float py0,
float x0, float y0,
float x1, float y1,
float omx, float omy, float mx, float my,
boolean rev) {
if (mx == omx && my == omy) {
return;
......@@ -461,11 +377,11 @@ public class Stroker extends LineSink {
miter);
// Compute miter length in untransformed coordinates
long dx = (long)miter[0] - x0;
long dy = (long)miter[1] - y0;
long a = (dy*m00 - dx*m10) >> 16;
long b = (dy*m01 - dx*m11) >> 16;
long lenSq = a*a + b*b;
float dx = miter[0] - x0;
float dy = miter[1] - y0;
float a = dy*m00 - dx*m10;
float b = dy*m01 - dx*m11;
float lenSq = a*a + b*b;
if (lenSq < miterLimitSq) {
emitLineTo(miter[0], miter[1], rev);
......@@ -473,7 +389,7 @@ public class Stroker extends LineSink {
}
public void moveTo(int x0, int y0) {
public void moveTo(float x0, float y0) {
// System.out.println("Stroker.moveTo(" + x0/65536.0 + ", " + y0/65536.0 + ")");
if (lineToOrigin) {
......@@ -501,7 +417,7 @@ public class Stroker extends LineSink {
this.joinSegment = true;
}
public void lineTo(int x1, int y1) {
public void lineTo(float x1, float y1) {
// System.out.println("Stroker.lineTo(" + x1/65536.0 + ", " + y1/65536.0 + ")");
if (lineToOrigin) {
......@@ -526,10 +442,10 @@ public class Stroker extends LineSink {
joinSegment = false;
}
private void lineToImpl(int x1, int y1, boolean joinSegment) {
private void lineToImpl(float x1, float y1, boolean joinSegment) {
computeOffset(x0, y0, x1, y1, offset);
int mx = offset[0];
int my = offset[1];
float mx = offset[0];
float my = offset[1];
if (!started) {
emitMoveTo(x0 + mx, y0 + my);
......@@ -567,10 +483,6 @@ public class Stroker extends LineSink {
emitLineTo(x0 - mx, y0 - my, true);
emitLineTo(x1 - mx, y1 - my, true);
lx0 = x1 + mx; ly0 = y1 + my;
lx0p = x1 - mx; ly0p = y1 - my;
lx1 = x1; ly1 = y1;
this.omx = mx;
this.omy = my;
this.px0 = x0;
......@@ -594,8 +506,8 @@ public class Stroker extends LineSink {
}
computeOffset(x0, y0, sx0, sy0, offset);
int mx = offset[0];
int my = offset[1];
float mx = offset[0];
float my = offset[1];
// Draw penultimate join
boolean ccw = isCCW(px0, py0, x0, y0, sx0, sy0);
......@@ -678,12 +590,10 @@ public class Stroker extends LineSink {
this.prev = MOVE_TO;
}
long lineLength(long ldx, long ldy) {
long ldet = ((long)m00*m11 - (long)m01*m10) >> 16;
long la = ((long)ldy*m00 - (long)ldx*m10)/ldet;
long lb = ((long)ldy*m01 - (long)ldx*m11)/ldet;
long llen = (int)PiscesMath.hypot(la, lb);
return llen;
double userSpaceLineLength(double dx, double dy) {
double a = (dy*m00 - dx*m10)/det;
double b = (dy*m01 - dx*m11)/det;
return Math.hypot(a, b);
}
private void finish() {
......@@ -692,13 +602,13 @@ public class Stroker extends LineSink {
omx, omy, -omx, -omy, 1, false, false,
ROUND_JOIN_THRESHOLD);
} else if (capStyle == CAP_SQUARE) {
long ldx = (long)(px0 - x0);
long ldy = (long)(py0 - y0);
long llen = lineLength(ldx, ldy);
long s = (long)lineWidth2*65536/llen;
float dx = px0 - x0;
float dy = py0 - y0;
float len = (float)userSpaceLineLength(dx, dy);
float s = lineWidth2/len;
int capx = x0 - (int)(ldx*s >> 16);
int capy = y0 - (int)(ldy*s >> 16);
float capx = x0 - dx*s;
float capy = y0 - dy*s;
emitLineTo(capx + omx, capy + omy);
emitLineTo(capx - omx, capy - omy);
......@@ -714,13 +624,13 @@ public class Stroker extends LineSink {
-mx0, -my0, mx0, my0, 1, false, false,
ROUND_JOIN_THRESHOLD);
} else if (capStyle == CAP_SQUARE) {
long ldx = (long)(sx1 - sx0);
long ldy = (long)(sy1 - sy0);
long llen = lineLength(ldx, ldy);
long s = (long)lineWidth2*65536/llen;
float dx = sx1 - sx0;
float dy = sy1 - sy0;
float len = (float)userSpaceLineLength(dx, dy);
float s = lineWidth2/len;
int capx = sx0 - (int)(ldx*s >> 16);
int capy = sy0 - (int)(ldy*s >> 16);
float capx = sx0 - dx*s;
float capy = sy0 - dy*s;
emitLineTo(capx - mx0, capy - my0);
emitLineTo(capx + mx0, capy + my0);
......@@ -730,17 +640,17 @@ public class Stroker extends LineSink {
this.joinSegment = false;
}
private void emitMoveTo(int x0, int y0) {
private void emitMoveTo(float x0, float y0) {
// System.out.println("Stroker.emitMoveTo(" + x0/65536.0 + ", " + y0/65536.0 + ")");
output.moveTo(x0, y0);
}
private void emitLineTo(int x1, int y1) {
private void emitLineTo(float x1, float y1) {
// System.out.println("Stroker.emitLineTo(" + x0/65536.0 + ", " + y0/65536.0 + ")");
output.lineTo(x1, y1);
}
private void emitLineTo(int x1, int y1, boolean rev) {
private void emitLineTo(float x1, float y1, boolean rev) {
if (rev) {
ensureCapacity(rindex + 2);
reverse[rindex++] = x1;
......@@ -755,3 +665,4 @@ public class Stroker extends LineSink {
output.close();
}
}
/*
* Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 in the LICENSE file that
* accompanied this code).
*
* 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.java2d.pisces;
public class Transform4 {
public int m00, m01, m10, m11;
// double det; // det*65536
public Transform4() {
this(1 << 16, 0, 0, 1 << 16);
}
public Transform4(int m00, int m01,
int m10, int m11) {
this.m00 = m00;
this.m01 = m01;
this.m10 = m10;
this.m11 = m11;
// this.det = (double)m00*m11 - (double)m01*m10;
}
// public Transform4 createInverse() {
// double dm00 = m00/65536.0;
// double dm01 = m01/65536.0;
// double dm10 = m10/65536.0;
// double dm11 = m11/65536.0;
// double invdet = 65536.0/(dm00*dm11 - dm01*dm10);
// int im00 = (int)( dm11*invdet);
// int im01 = (int)(-dm01*invdet);
// int im10 = (int)(-dm10*invdet);
// int im11 = (int)( dm00*invdet);
// return new Transform4(im00, im01, im10, im11);
// }
// public void transform(int[] point) {
// }
// /**
// * Returns the length of the line segment obtained by inverse
// * transforming the points <code>(x0, y0)</code> and <code>(x1,
// * y1)</code>.
// */
// public int getTransformedLength(int x0, int x1, int y0, int y1) {
// int lx = x1 - x0;
// int ly = y1 - y0;
// double a = (double)m00*ly - (double)m10*lx;
// double b = (double)m01*ly - (double)m11*lx;
// double len = PiscesMath.sqrt((a*a + b*b)/(det*det));
// return (int)(len*65536.0);
// }
// public int getType() {
// }
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册