提交 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; ...@@ -36,117 +36,71 @@ package sun.java2d.pisces;
* semantics are unclear. * 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; private final float m00, m10, m01, m11;
int[] dash; private final float det;
int startPhase;
boolean startDashOn;
int startIdx;
int idx; private boolean firstDashOn;
boolean dashOn; private boolean starting;
int phase;
int sx, sy; private int idx;
int x0, y0; private boolean dashOn;
private float phase;
int m00, m01; private float sx, sy;
int m10, m11; 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>. * Constructs a <code>Dasher</code>.
* *
* @param output an output <code>LineSink</code>. * @param output an output <code>LineSink</code>.
* @param dash an array of <code>int</code>s containing the dash * @param dash an array of <code>int</code>s containing the dash pattern
* pattern in S15.16 format. * @param phase an <code>int</code> containing the dash phase
* @param phase an <code>int</code> containing the dash phase in
* S15.16 format.
* @param transform a <code>Transform4</code> object indicating * @param transform a <code>Transform4</code> object indicating
* the transform that has been previously applied to all incoming * the transform that has been previously applied to all incoming
* coordinates. This is required in order to compute dash lengths * coordinates. This is required in order to compute dash lengths
* properly. * properly.
*/ */
public Dasher(LineSink output, public Dasher(LineSink output,
int[] dash, int phase, float[] dash, float phase,
Transform4 transform) { float a00, float a01, float a10, float a11) {
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) {
if (phase < 0) { if (phase < 0) {
throw new IllegalArgumentException("phase < 0 !"); throw new IllegalArgumentException("phase < 0 !");
} }
this.output = output;
// Normalize so 0 <= phase < dash[0] // Normalize so 0 <= phase < dash[0]
int idx = 0; int idx = 0;
dashOn = true; dashOn = true;
int d; float d;
while (phase >= (d = dash[idx])) { while (phase >= (d = dash[idx])) {
phase -= d; phase -= d;
idx = (idx + 1) % dash.length; idx = (idx + 1) % dash.length;
dashOn = !dashOn; dashOn = !dashOn;
} }
this.dash = new int[dash.length]; this.dash = dash;
for (int i = 0; i < dash.length; i++) {
this.dash[i] = dash[i];
}
this.startPhase = this.phase = phase; this.startPhase = this.phase = phase;
this.startDashOn = dashOn; this.startDashOn = dashOn;
this.startIdx = idx; this.startIdx = idx;
this.transform = transform; m00 = a00;
m01 = a01;
this.m00 = transform.m00; m10 = a10;
this.m01 = transform.m01; m11 = a11;
this.m10 = transform.m10; det = m00 * m11 - m01 * m10;
this.m11 = transform.m11;
this.ldet = ((long)m00*m11 - (long)m01*m10) >> 16;
this.symmetric = (m00 == m11 && m10 == -m01);
} }
public void moveTo(int x0, int y0) { public void moveTo(float x0, float y0) {
output.moveTo(x0, y0); output.moveTo(x0, y0);
this.idx = startIdx; this.idx = startIdx;
this.dashOn = this.startDashOn; this.dashOn = this.startDashOn;
...@@ -160,7 +114,7 @@ public class Dasher extends LineSink { ...@@ -160,7 +114,7 @@ public class Dasher extends LineSink {
output.lineJoin(); output.lineJoin();
} }
private void goTo(int x1, int y1) { private void goTo(float x1, float y1) {
if (dashOn) { if (dashOn) {
if (starting) { if (starting) {
this.sx1 = x1; this.sx1 = x1;
...@@ -180,52 +134,64 @@ public class Dasher extends LineSink { ...@@ -180,52 +134,64 @@ public class Dasher extends LineSink {
this.y0 = y1; this.y0 = y1;
} }
public void lineTo(int x1, int y1) { public void lineTo(float x1, float y1) {
while (true) { // The widened line is squished to a 0 width one, so no drawing is done
int d = dash[idx] - phase; if (det == 0) {
int lx = x1 - x0; goTo(x1, y1);
int ly = y1 - y0; return;
}
// Compute segment length in the untransformed float dx = x1 - x0;
// coordinate system float dy = y1 - y0;
// IMPL NOTE - use fixed point
int l; // Compute segment length in the untransformed
if (symmetric) { // coordinate system
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);
}
if (l < d) { 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;
}
// 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); goTo(x1, y1);
// Advance phase within current dash segment // 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; return;
} }
long t; float dashx, dashy;
int xsplit, ysplit; float dashdx = dash[idx] * cx;
// // For zero length dashses, SE appears to move 1/8 unit float dashdy = dash[idx] * cy;
// // in device space if (phase == 0) {
// if (d == 0) { dashx = x0 + dashdx;
// double dlx = lx/65536.0; dashy = y0 + dashdy;
// double dly = ly/65536.0; } else {
// len = PiscesMath.hypot(dlx, dly); float p = (leftInThisDashSegment) / dash[idx];
// double dt = 1.0/(8*len); dashx = x0 + p * dashdx;
// double dxsplit = (x0/65536.0) + dt*dlx; dashy = y0 + p * dashdy;
// 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);
goTo(dashx, dashy);
origLen -= (dash[idx] - phase);
// Advance to next dash segment // Advance to next dash segment
idx = (idx + 1) % dash.length; idx = (idx + 1) % dash.length;
dashOn = !dashOn; dashOn = !dashOn;
...@@ -233,6 +199,7 @@ public class Dasher extends LineSink { ...@@ -233,6 +199,7 @@ public class Dasher extends LineSink {
} }
} }
public void close() { public void close() {
lineTo(sx, sy); lineTo(sx, sy);
if (firstDashOn) { if (firstDashOn) {
......
...@@ -39,16 +39,16 @@ package sun.java2d.pisces; ...@@ -39,16 +39,16 @@ package sun.java2d.pisces;
* <code>LineSink</code> interface. * <code>LineSink</code> interface.
* *
*/ */
public abstract class LineSink { public interface LineSink {
/** /**
* Moves the current drawing position to the point <code>(x0, * Moves the current drawing position to the point <code>(x0,
* y0)</code>. * y0)</code>.
* *
* @param x0 the X coordinate in S15.16 format * @param x0 the X coordinate
* @param y0 the Y coordinate in S15.16 format * @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 * Provides a hint that the current segment should be joined to
...@@ -65,29 +65,29 @@ public abstract class LineSink { ...@@ -65,29 +65,29 @@ public abstract class LineSink {
* <p> Other <code>LineSink</code> classes should simply pass this * <p> Other <code>LineSink</code> classes should simply pass this
* hint to their output sink as needed. * 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 * 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> and sets the current drawing position to
* <code>(x1, y1)</code>. * <code>(x1, y1)</code>.
* *
* @param x1 the X coordinate in S15.16 format * @param x1 the X coordinate
* @param y1 the Y coordinate in S15.16 format * @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 * Closes the current path by drawing a line from the current
* drawing position to the point specified by the moset recent * drawing position to the point specified by the moset recent
* <code>moveTo</code> command. * <code>moveTo</code> command.
*/ */
public abstract void close(); public void close();
/** /**
* Ends the current path. It may be necessary to end a path in * Ends the current path. It may be necessary to end a path in
* order to allow end caps to be drawn. * 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; ...@@ -37,24 +37,8 @@ import sun.java2d.pipe.RenderingEngine;
import sun.java2d.pipe.AATileGenerator; import sun.java2d.pipe.AATileGenerator;
public class PiscesRenderingEngine extends RenderingEngine { public class PiscesRenderingEngine extends RenderingEngine {
public static Transform4 IdentT4 = new Transform4();
public static double defaultFlat = 0.1; 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. * Create a widened path as specified by the parameters.
* <p> * <p>
...@@ -85,18 +69,19 @@ public class PiscesRenderingEngine extends RenderingEngine { ...@@ -85,18 +69,19 @@ public class PiscesRenderingEngine extends RenderingEngine {
strokeTo(src, strokeTo(src,
null, null,
width, width,
false,
caps, caps,
join, join,
miterlimit, miterlimit,
dashes, dashes,
dashphase, dashphase,
new LineSink() { new LineSink() {
public void moveTo(int x0, int y0) { public void moveTo(float x0, float y0) {
p2d.moveTo(S15_16ToFloat(x0), S15_16ToFloat(y0)); p2d.moveTo(x0, y0);
} }
public void lineJoin() {} public void lineJoin() {}
public void lineTo(int x1, int y1) { public void lineTo(float x1, float y1) {
p2d.lineTo(S15_16ToFloat(x1), S15_16ToFloat(y1)); p2d.lineTo(x1, y1);
} }
public void close() { public void close() {
p2d.closePath(); p2d.closePath();
...@@ -144,12 +129,12 @@ public class PiscesRenderingEngine extends RenderingEngine { ...@@ -144,12 +129,12 @@ public class PiscesRenderingEngine extends RenderingEngine {
{ {
strokeTo(src, at, bs, thin, normalize, antialias, strokeTo(src, at, bs, thin, normalize, antialias,
new LineSink() { new LineSink() {
public void moveTo(int x0, int y0) { public void moveTo(float x0, float y0) {
consumer.moveTo(S15_16ToFloat(x0), S15_16ToFloat(y0)); consumer.moveTo(x0, y0);
} }
public void lineJoin() {} public void lineJoin() {}
public void lineTo(int x1, int y1) { public void lineTo(float x1, float y1) {
consumer.lineTo(S15_16ToFloat(x1), S15_16ToFloat(y1)); consumer.lineTo(x1, y1);
} }
public void close() { public void close() {
consumer.closePath(); consumer.closePath();
...@@ -181,6 +166,7 @@ public class PiscesRenderingEngine extends RenderingEngine { ...@@ -181,6 +166,7 @@ public class PiscesRenderingEngine extends RenderingEngine {
strokeTo(src, strokeTo(src,
at, at,
lw, lw,
normalize,
bs.getEndCap(), bs.getEndCap(),
bs.getLineJoin(), bs.getLineJoin(),
bs.getMiterLimit(), bs.getMiterLimit(),
...@@ -258,6 +244,7 @@ public class PiscesRenderingEngine extends RenderingEngine { ...@@ -258,6 +244,7 @@ public class PiscesRenderingEngine extends RenderingEngine {
void strokeTo(Shape src, void strokeTo(Shape src,
AffineTransform at, AffineTransform at,
float width, float width,
boolean normalize,
int caps, int caps,
int join, int join,
float miterlimit, float miterlimit,
...@@ -265,32 +252,16 @@ public class PiscesRenderingEngine extends RenderingEngine { ...@@ -265,32 +252,16 @@ public class PiscesRenderingEngine extends RenderingEngine {
float dashphase, float dashphase,
LineSink lsink) LineSink lsink)
{ {
Transform4 t4; float a00 = 1f, a01 = 0f, a10 = 0f, a11 = 1f;
if (at != null && !at.isIdentity()) {
if (at == null || at.isIdentity()) { a00 = (float)at.getScaleX();
t4 = IdentT4; a01 = (float)at.getShearX();
} else { a10 = (float)at.getShearY();
t4 = new Transform4(FloatToS15_16((float) at.getScaleX()), a11 = (float)at.getScaleY();
FloatToS15_16((float) at.getShearX()),
FloatToS15_16((float) at.getShearY()),
FloatToS15_16((float) at.getScaleY()));
} }
lsink = new Stroker(lsink, width, caps, join, miterlimit, a00, a01, a10, a11);
lsink = new Stroker(lsink,
FloatToS15_16(width),
caps,
join,
FloatToS15_16(miterlimit),
t4);
if (dashes != null) { if (dashes != null) {
int fdashes[] = new int[dashes.length]; lsink = new Dasher(lsink, dashes, dashphase, a00, a01, a10, a11);
for (int i = 0; i < dashes.length; i++) {
fdashes[i] = FloatToS15_16(dashes[i]);
}
lsink = new Dasher(lsink,
fdashes,
FloatToS15_16(dashphase),
t4);
} }
PathIterator pi = src.getPathIterator(at, defaultFlat); PathIterator pi = src.getPathIterator(at, defaultFlat);
...@@ -302,13 +273,11 @@ public class PiscesRenderingEngine extends RenderingEngine { ...@@ -302,13 +273,11 @@ public class PiscesRenderingEngine extends RenderingEngine {
while (!pi.isDone()) { while (!pi.isDone()) {
switch (pi.currentSegment(coords)) { switch (pi.currentSegment(coords)) {
case PathIterator.SEG_MOVETO: case PathIterator.SEG_MOVETO:
lsink.moveTo(FloatToS15_16(coords[0]), lsink.moveTo(coords[0], coords[1]);
FloatToS15_16(coords[1]));
break; break;
case PathIterator.SEG_LINETO: case PathIterator.SEG_LINETO:
lsink.lineJoin(); lsink.lineJoin();
lsink.lineTo(FloatToS15_16(coords[0]), lsink.lineTo(coords[0], coords[1]);
FloatToS15_16(coords[1]));
break; break;
case PathIterator.SEG_CLOSE: case PathIterator.SEG_CLOSE:
lsink.lineJoin(); lsink.lineJoin();
...@@ -378,17 +347,19 @@ public class PiscesRenderingEngine extends RenderingEngine { ...@@ -378,17 +347,19 @@ public class PiscesRenderingEngine extends RenderingEngine {
int bbox[]) int bbox[])
{ {
PiscesCache pc = PiscesCache.createInstance(); PiscesCache pc = PiscesCache.createInstance();
Renderer r = new Renderer(); Renderer r;
r.setCache(pc);
r.setAntialiasing(3, 3);
r.beginRendering(clip.getLoX(), clip.getLoY(),
clip.getWidth(), clip.getHeight());
if (bs == null) { if (bs == null) {
PathIterator pi = s.getPathIterator(at, defaultFlat); 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); pathTo(pi, r);
} else { } 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); strokeTo(s, at, bs, thin, normalize, true, r);
} }
r.endRendering(); r.endRendering();
......
...@@ -25,629 +25,478 @@ ...@@ -25,629 +25,478 @@
package sun.java2d.pisces; package sun.java2d.pisces;
public class Renderer extends LineSink { import java.util.Arrays;
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;
// 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;
// Pixel bounding box for current primitive
private int bboxX0, bboxY0, bboxX1, bboxY1;
// Current winding rule
private int windingRule;
// Current drawing position, i.e., final point of last segment public class Renderer implements LineSink {
private int x0, y0;
// Position of most recent 'moveTo' command
private int sx0, sy0;
// Buffer to be filled with one row's worth of alpha values
private byte[] rowAA; // needs to be short if 16x16 subsampling
// 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;
// Parameters for emitRow
private int alphaWidth;
public Renderer() {
}
public void setAntialiasing(int subpixelLgPositionsX, ///////////////////////////////////////////////////////////////////////////////
int subpixelLgPositionsY) { // Scan line iterator and edge crossing data.
this.SUBPIXEL_LG_POSITIONS_X = subpixelLgPositionsX; //////////////////////////////////////////////////////////////////////////////
this.SUBPIXEL_LG_POSITIONS_Y = subpixelLgPositionsY;
this.SUBPIXEL_MASK_X = private int[] crossings;
(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);
}
public int getSubpixelLgPositionsX() { // This is an array of indices into the edge array. It is initialized to
return SUBPIXEL_LG_POSITIONS_X; // [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;
// 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;
// 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;
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;
}
qsort(0, edgePtrs.length - 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));
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);
}
// 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;
}
private int ScanLineItCurrentY() {
return nextY - 1;
}
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++];
}
}
if (hi - lo > crossings.length) {
int newSize = Math.max(hi - lo, crossings.length * 2);
crossings = Arrays.copyOf(crossings, newSize);
}
// 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 int getSubpixelLgPositionsY() { nextY++;
return SUBPIXEL_LG_POSITIONS_Y; // Expand active list to include new edges.
} for (; hi < edgePtrs.length && edges[edgePtrs[hi]+CURY] <= nextY; hi++) {
setCurY(edgePtrs[hi], nextY);
}
public void setWindingRule(int windingRule) { Arrays.sort(crossings, 0, crossingIdx);
this.windingRule = windingRule; return crossingIdx;
} }
public int getWindingRule() { private boolean ScanLineItHasNext() {
return windingRule; return nextY < maxY;
} }
public void beginRendering(int boundsX, int boundsY, private void addCrossing(int y, int x, int or, int idx) {
int boundsWidth, int boundsHeight) { if (x < minX) {
lastOrientation = 0; minX = x;
flips = 0; }
if (x > maxX) {
resetEdges(); maxX = x;
}
this.boundsMinX = boundsX << 16; x <<= 1;
this.boundsMinY = boundsY << 16; crossings[idx] = ((or == 1) ? (x | 0x1) : x);
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;
}
public void lineJoin() { // quicksort implementation for sorting the edge indices ("pointers")
// System.out.println("Renderer: lineJoin"); // by increasing y0. first, last are indices into the "pointer" array
// do nothing // 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);
}
if (p < last) {
qsort(p, last);
}
}
} }
public void lineTo(int x1, int y1) { // i, j are indices into edgePtrs.
// System.out.println("Renderer: lineTo " + x1/65536.0 + " " + y1/65536.0); private int partition(int i, int j) {
int pivotVal = edgePtrs[i];
// Ignore horizontal lines while (i <= j) {
// Next line will count flip // edges[edgePtrs[i]+1] is equivalent to (*(edgePtrs[i])).y0 in C
if (y0 == y1) { while (edges[edgePtrs[i]+CURY] < edges[pivotVal+CURY]) { i++; }
this.x0 = x1; 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--;
}
}
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; return;
} }
int orientation = (y0 < y1) ? 1 : -1; int newSize = edgesSize + SIZEOF_STRUCT_EDGE;
if (lastOrientation == 0) { if (edges.length < newSize) {
firstOrientation = orientation; edges = Arrays.copyOf(edges, newSize * 2);
} else if (orientation != lastOrientation) {
++flips;
} }
lastOrientation = orientation; edges[edgesSize+CURX] = x0;
edges[edgesSize+CURY] = y0;
// Bias Y by 1 ULP so endpoints never lie on a scanline edges[edgesSize+Y1] = y1;
addEdge(x0, y0 | 0x1, x1, y1 | 0x1); 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;
this.x0 = x1; // Accumulate edgeMinY and edgeMaxY
this.y0 = y1; if (y0 < edgeMinY) { edgeMinY = y0; }
if (y1 > edgeMaxY) { edgeMaxY = y1; }
} }
public void close() { // As far as the following methods care, this edges extends to infinity.
// System.out.println("Renderer: close"); // 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;
}
int orientation = lastOrientation; private void gotoNextY(int idx) {
if (y0 != sy0) { edges[idx + CURY] += 1f; // i.e. curY += 1
orientation = (y0 < sy0) ? 1 : -1; edges[idx + CURX] += edges[idx + SLOPE]; // i.e. curXCrossing += slope
}
if (orientation != firstOrientation) {
++flips;
}
lineTo(sx0, sy0);
} }
public void end() { private int getCurCrossing(int idx) {
close(); return (int)edges[idx + CURX];
// System.out.println("Renderer: end");
// do nothing
} }
//====================================================================================
// Scan convert a single edge public static final int WIND_EVEN_ODD = 0;
private void computeCrossingsForEdge(int index, public static final int WIND_NON_ZERO = 1;
int boundsMinY, int boundsMaxY) {
int iy0 = edges[index + 1];
int iy1 = edges[index + 3];
// Clip to valid Y range // Antialiasing
int clipy0 = (iy0 > boundsMinY) ? iy0 : boundsMinY; final private int SUBPIXEL_LG_POSITIONS_X;
int clipy1 = (iy1 < boundsMaxY) ? iy1 : boundsMaxY; 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 = ((clipy0 + HYSTEP) & YMASK) + HYSTEP; // Cache to store RLE-encoded coverage mask of the current primitive
int maxY = ((clipy1 - HYSTEP) & YMASK) + HYSTEP; final PiscesCache cache;
// IMPL_NOTE - If line falls outside the valid X range, could // Bounds of the drawing region, at subpixel precision.
// draw a vertical line instead final private int boundsMinX, boundsMinY, boundsMaxX, boundsMaxY;
// Exit if no scanlines are crossed // Pixel bounding box for current primitive
if (minY > maxY) { private int pix_bboxX0, pix_bboxY0, pix_bboxX1, pix_bboxY1;
return;
}
// Scan convert line using a DDA approach // Current winding rule
final private int windingRule;
int ix0 = edges[index]; // Current drawing position, i.e., final point of last segment
int ix1 = edges[index + 2]; private float x0, y0;
long dx = ((long) ix1) - ix0;
long dy = ((long) iy1) - iy0;
// Compute first crossing point at y = minY // Position of most recent 'moveTo' command
int orientation = edges[index + 4]; private float pix_sx0, pix_sy0;
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 public Renderer(int subpixelLgPositionsX, int subpixelLgPositionsY,
y += YSTEP; int pix_boundsX, int pix_boundsY,
if (y > maxY) { int pix_boundsWidth, int pix_boundsHeight,
return; 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 xstep only if additional scanlines are crossed this.edges = new float[SIZEOF_STRUCT_EDGE * INIT_NUM_EDGES];
// For each scanline, add xstep to lx and YSTEP to y and edgeMinY = Float.POSITIVE_INFINITY;
// emit the new crossing edgeMaxY = Float.NEGATIVE_INFINITY;
long xstep = ((long)YSTEP*dx)/dy; edgesSize = 0;
for (; y <= maxY; y += YSTEP) {
lx += xstep;
addCrossing(y >> YSHIFT, (int)(lx >> XSHIFT), orientation);
}
}
private void computeBounds() { this.windingRule = windingRule;
rasterMinX = crossingMinX & ~SUBPIXEL_MASK_X; this.cache = cache;
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) { this.boundsMinX = pix_boundsX * SUBPIXEL_POSITIONS_X;
rasterMinX = boundsMinX >> XSHIFT; this.boundsMinY = pix_boundsY * SUBPIXEL_POSITIONS_Y;
} this.boundsMaxX = (pix_boundsX + pix_boundsWidth) * SUBPIXEL_POSITIONS_X;
if (rasterMinY < boundsMinY >> YSHIFT) { this.boundsMaxY = (pix_boundsY + pix_boundsHeight) * SUBPIXEL_POSITIONS_Y;
rasterMinY = boundsMinY >> YSHIFT;
}
if (rasterMaxX > boundsMaxX >> XSHIFT) {
rasterMaxX = boundsMaxX >> XSHIFT;
}
if (rasterMaxY > boundsMaxY >> YSHIFT) {
rasterMaxY = boundsMaxY >> YSHIFT;
}
}
private int clamp(int x, int min, int max) { this.pix_bboxX0 = pix_boundsX;
if (x < min) { this.pix_bboxY0 = pix_boundsY;
return min; this.pix_bboxX1 = pix_boundsX + pix_boundsWidth;
} else if (x > max) { this.pix_bboxY1 = pix_boundsY + pix_boundsHeight;
return max;
}
return x;
} }
private void _endRendering() { private float tosubpixx(float pix_x) {
if (flips == 0) { return pix_x * SUBPIXEL_POSITIONS_X;
bboxX0 = bboxY0 = 0; }
bboxX1 = bboxY1 = -1; private float tosubpixy(float pix_y) {
return; return pix_y * SUBPIXEL_POSITIONS_Y;
} }
// 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;
}
if (y0 > y1) {
int tmp = y0;
y0 = y1;
y1 = tmp;
}
int bMinX = this.boundsMinX >> XSHIFT; public void moveTo(float pix_x0, float pix_y0) {
int bMinY = this.boundsMinY >> YSHIFT; close();
int bMaxX = this.boundsMaxX >> XSHIFT; this.pix_sx0 = pix_x0;
int bMaxY = this.boundsMaxY >> YSHIFT; this.pix_sy0 = pix_y0;
this.y0 = tosubpixy(pix_y0);
// Clip to image bounds in supersampled coordinates this.x0 = tosubpixx(pix_x0);
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);
*/
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;
return; public void lineJoin() { /* do nothing */ }
}
int minY = (edgeMinY > boundsMinY) ? edgeMinY : boundsMinY; public void lineTo(float pix_x1, float pix_y1) {
int maxY = (edgeMaxY < boundsMaxY) ? edgeMaxY : boundsMaxY; float x1 = tosubpixx(pix_x1);
float y1 = tosubpixy(pix_y1);
// Check for empty intersection of primitive with the drawing area // Ignore horizontal lines
if (minY > maxY) { if (y0 == y1) {
bboxX0 = bboxY0 = 0; this.x0 = x1;
bboxX1 = bboxY1 = -1;
return; return;
} }
// Compute Y extent in subpixel coordinates addEdge(x0, y0, x1, y1);
int iminY = (minY >> YSHIFT) & ~SUBPIXEL_MASK_Y;
int imaxY = (maxY >> YSHIFT) | SUBPIXEL_MASK_Y;
int yextent = (imaxY - iminY) + 1;
// Maximum number of crossings
int size = flips*yextent;
int bmax = (boundsMaxY >> YSHIFT) - 1;
if (imaxY > bmax) {
imaxY = bmax;
}
// Initialize X bounds, will be refined for each strip
bboxX0 = Integer.MAX_VALUE;
bboxX1 = Integer.MIN_VALUE;
// Set Y bounds
bboxY0 = iminY >> SUBPIXEL_LG_POSITIONS_Y;
bboxY1 = (imaxY + SUBPIXEL_POSITIONS_Y - 1) >> SUBPIXEL_LG_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);
int bminY = i << YSHIFT;
int bmaxY = (last << YSHIFT) | ~YMASK;
// 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 ];
maxIdx -= 5;
index -= 5;
continue;
}
// Test y0 > max: this.x0 = x1;
// this.y0 = y1;
// If edge lies entirely below current strip,
// skip it for now
if (edges[index + 1] > bmaxY) {
continue;
}
computeCrossingsForEdge(index, bminY, bmaxY);
}
computeBounds();
if (rasterMaxX < rasterMinX) {
continue;
}
bboxX0 = Math.min(bboxX0,
rasterMinX >> SUBPIXEL_LG_POSITIONS_X);
bboxX1 = Math.max(bboxX1,
(rasterMaxX + SUBPIXEL_POSITIONS_X - 1)
>> SUBPIXEL_LG_POSITIONS_X);
renderStrip();
}
// Free up any unusually large scratchpad memory used by the
// preceding primitive
crossingListFinished();
} }
public void endRendering() { public void close() {
// Set up the cache to accumulate the bounding box // lineTo expects its input in pixel coordinates.
if (cache != null) { lineTo(pix_sx0, pix_sy0);
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[] bbox) { public void end() {
bbox[0] = bboxX0; close();
bbox[1] = bboxY0;
bbox[2] = bboxX1 - bboxX0;
bbox[3] = bboxY1 - bboxY0;
} }
private void renderStrip() { private void _endRendering() {
// Grow rowAA according to the raster width
int width = (rasterMaxX - rasterMinX + 1) >> SUBPIXEL_LG_POSITIONS_X;
alphaWidth = width;
// 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];
}
// Mask to determine the relevant bit of the crossing sum // Mask to determine the relevant bit of the crossing sum
// 0x1 if EVEN_ODD, all bits if NON_ZERO // 0x1 if EVEN_ODD, all bits if NON_ZERO
int mask = (windingRule == WIND_EVEN_ODD) ? 0x1 : ~0x0; int mask = (windingRule == WIND_EVEN_ODD) ? 0x1 : ~0x0;
int y = 0; // add 1 to better deal with the last pixel in a pixel row.
int prevY = rasterMinY - 1; int width = ((boundsMaxX - boundsMinX) >> SUBPIXEL_LG_POSITIONS_X) + 1;
byte[] alpha = new byte[width+1];
int minX = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE; // Now we iterate through the scanlines. We must tell emitRow the coord
// of the first non-transparent pixel, so we must keep accumulators for
iterateCrossings(); // the first and last pixels of the section of the current pixel row
while (hasMoreCrossingRows()) { // that we will emit.
y = crossingY; // 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
// Emit any skipped rows int pix_maxX = Integer.MIN_VALUE;
for (int j = prevY + 1; j < y; j++) { int pix_minX = Integer.MAX_VALUE;
if (((j & SUBPIXEL_MASK_Y) == SUBPIXEL_MASK_Y) ||
(j == rasterMaxY)) { int y = boundsMinY; // needs to be declared here so we emit the last row properly.
emitRow(j >> SUBPIXEL_LG_POSITIONS_Y, 0, -1); ScanLineItInitialize();
} for ( ; ScanLineItHasNext(); ) {
} int numCrossings = ScanLineItGoToNextYAndComputeCrossings();
prevY = y; y = ScanLineItCurrentY();
if (crossingRowIndex < crossingRowCount) { if (numCrossings > 0) {
int lx = crossings[crossingRowOffset + crossingRowIndex]; int lowx = crossings[0] >> 1;
lx >>= 1; int highx = crossings[numCrossings - 1] >> 1;
int hx = crossings[crossingRowOffset + crossingRowCount - 1]; int x0 = Math.max(lowx, boundsMinX);
hx >>= 1; int x1 = Math.min(highx, boundsMaxX);
int x0 = lx > rasterMinX ? lx : rasterMinX;
int x1 = hx < rasterMaxX ? hx : rasterMaxX; pix_minX = Math.min(pix_minX, x0 >> SUBPIXEL_LG_POSITIONS_X);
x0 -= rasterMinX; pix_maxX = Math.max(pix_maxX, x1 >> SUBPIXEL_LG_POSITIONS_X);
x1 -= rasterMinX;
minX = Math.min(minX, x0 >> SUBPIXEL_LG_POSITIONS_X);
maxX = Math.max(maxX, x1 >> SUBPIXEL_LG_POSITIONS_X);
} }
int sum = 0; int sum = 0;
int prev = rasterMinX; int prev = boundsMinX;
while (crossingRowIndex < crossingRowCount) { for (int i = 0; i < numCrossings; i++) {
int crxo = crossings[crossingRowOffset + crossingRowIndex]; int curxo = crossings[i];
crossingRowIndex++; 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 pix_x = x0 >> SUBPIXEL_LG_POSITIONS_X;
int crorientation = ((crxo & 0x1) == 0x1) ? 1 : -1; int pix_xmaxm1 = (x1 - 1) >> SUBPIXEL_LG_POSITIONS_X;
if ((sum & mask) != 0) { if (pix_x == pix_xmaxm1) {
// 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) {
// Start and end in same pixel // Start and end in same pixel
rowAA[x] += x1 - x0; alpha[pix_x] += (x1 - x0);
alpha[pix_x+1] -= (x1 - x0);
} else { } else {
// Start and end in different pixels int pix_xmax = x1 >> SUBPIXEL_LG_POSITIONS_X;
rowAA[x++] += SUBPIXEL_POSITIONS_X - alpha[pix_x] += SUBPIXEL_POSITIONS_X - (x0 & SUBPIXEL_MASK_X);
(x0 & SUBPIXEL_MASK_X); alpha[pix_x+1] += (x0 & SUBPIXEL_MASK_X);
int xmax = x1 >> SUBPIXEL_LG_POSITIONS_X; alpha[pix_xmax] -= SUBPIXEL_POSITIONS_X - (x1 & SUBPIXEL_MASK_X);
while (x < xmax) { alpha[pix_xmax+1] -= (x1 & SUBPIXEL_MASK_X);
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;
} }
} }
} }
sum += crorientation; sum += crorientation;
prev = crx; prev = curx;
} }
// Every SUBPIXEL_POSITIONS rows, output an antialiased row if ((y & SUBPIXEL_MASK_Y) == SUBPIXEL_MASK_Y) {
if (((y & SUBPIXEL_MASK_Y) == SUBPIXEL_MASK_Y) || emitRow(alpha, y >> SUBPIXEL_LG_POSITIONS_Y, pix_minX, pix_maxX);
(y == rasterMaxY)) { pix_minX = Integer.MAX_VALUE;
emitRow(y >> SUBPIXEL_LG_POSITIONS_Y, minX, maxX); pix_maxX = Integer.MIN_VALUE;
minX = Integer.MAX_VALUE;
maxX = Integer.MIN_VALUE;
} }
} }
// Emit final row // Emit final row
for (int j = prevY + 1; j <= rasterMaxY; j++) { if (pix_maxX >= pix_minX) {
if (((j & SUBPIXEL_MASK_Y) == SUBPIXEL_MASK_Y) || emitRow(alpha, y >> SUBPIXEL_LG_POSITIONS_Y, pix_minX, pix_maxX);
(j == rasterMaxY)) {
emitRow(j >> SUBPIXEL_LG_POSITIONS_Y, minX, maxX);
minX = Integer.MAX_VALUE;
maxX = Integer.MIN_VALUE;
}
} }
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; public void endRendering() {
for (int i = 0; i < w; i++, aidx++) { // Set up the cache to accumulate the bounding box
alpha[aidx] = (byte)0; if (cache != null) {
} cache.bboxX0 = Integer.MAX_VALUE;
cache.bboxY0 = Integer.MAX_VALUE;
cache.bboxX1 = Integer.MIN_VALUE;
cache.bboxY1 = Integer.MIN_VALUE;
} }
_endRendering();
} }
private void emitRow(int y, int minX, int maxX) { 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(byte[] alphaRow, int pix_y, int pix_from, int pix_to) {
// Copy rowAA data into the cache if one is present // Copy rowAA data into the cache if one is present
if (cache != null) { if (cache != null) {
if (maxX >= minX) { if (pix_to >= pix_from) {
int x0 = minX + (rasterMinX >> SUBPIXEL_LG_POSITIONS_X); cache.startRow(pix_y, pix_from, pix_to);
int x1 = maxX + (rasterMinX >> SUBPIXEL_LG_POSITIONS_X);
cache.startRow(y, x0, x1); // Perform run-length encoding and store results in the cache
int srcIdx = minX; 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; int runLen = 1;
while (srcIdx <= maxX) { byte startVal = alphaRow[from];
byte nextVal = rowAA[srcIdx++]; for (int i = from + 1; i <= to; i++) {
byte nextVal = (byte)(startVal + alphaRow[i]);
if (nextVal == startVal && runLen < 255) { if (nextVal == startVal && runLen < 255) {
++runLen; runLen++;
} else { } else {
cache.addRLERun(startVal, runLen); cache.addRLERun(startVal, runLen);
runLen = 1; runLen = 1;
startVal = nextVal; startVal = nextVal;
} }
...@@ -656,190 +505,6 @@ public class Renderer extends LineSink { ...@@ -656,190 +505,6 @@ public class Renderer extends LineSink {
cache.addRLERun((byte)0, 0); cache.addRLERun((byte)0, 0);
} }
} }
java.util.Arrays.fill(alphaRow, (byte)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;
}
} }
} }
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
package sun.java2d.pisces; 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 MOVE_TO = 0;
private static final int LINE_TO = 1; private static final int LINE_TO = 1;
...@@ -61,19 +61,15 @@ public class Stroker extends LineSink { ...@@ -61,19 +61,15 @@ public class Stroker extends LineSink {
*/ */
public static final int CAP_SQUARE = 2; public static final int CAP_SQUARE = 2;
LineSink output; private final LineSink output;
int lineWidth; private final int capStyle;
int capStyle; private final int joinStyle;
int joinStyle;
int miterLimit;
Transform4 transform; private final float m00, m01, m10, m11, det;
int m00, m01;
int m10, m11;
int lineWidth2; private final float lineWidth2;
long scaledLineWidth2; private final float scaledLineWidth2;
// For any pen offset (pen_dx, pen_dy) that does not depend on // For any pen offset (pen_dx, pen_dy) that does not depend on
// the line orientation, the pen should be transformed so that: // the line orientation, the pen should be transformed so that:
...@@ -88,143 +84,86 @@ public class Stroker extends LineSink { ...@@ -88,143 +84,86 @@ public class Stroker extends LineSink {
// //
// pen_dx'(r, theta) = r*(m00*cos(theta) + m01*sin(theta)) // pen_dx'(r, theta) = r*(m00*cos(theta) + m01*sin(theta))
// pen_dy'(r, theta) = r*(m10*cos(theta) + m11*sin(theta)) // pen_dy'(r, theta) = r*(m10*cos(theta) + m11*sin(theta))
int numPenSegments; private int numPenSegments;
int[] pen_dx; private final float[] pen_dx;
int[] pen_dy; private final float[] pen_dy;
boolean[] penIncluded; private boolean[] penIncluded;
int[] join; private final float[] join;
int[] offset = new int[2]; private final float[] offset = new float[2];
int[] reverse = new int[100]; private float[] reverse = new float[100];
int[] miter = new int[2]; private final float[] miter = new float[2];
long miterLimitSq; private final float miterLimitSq;
int prev; private int prev;
int rindex; private int rindex;
boolean started; private boolean started;
boolean lineToOrigin; private boolean lineToOrigin;
boolean joinToOrigin; private boolean joinToOrigin;
int sx0, sy0, sx1, sy1, x0, y0, x1, y1; private float sx0, sy0, sx1, sy1, x0, y0, px0, py0;
int mx0, my0, mx1, my1, omx, omy; private float mx0, my0, omx, omy;
int lx0, ly0, lx1, ly1, lx0p, ly0p, px0, py0;
private float m00_2_m01_2;
double m00_2_m01_2; private float m10_2_m11_2;
double m10_2_m11_2; private float m00_m10_m01_m11;
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() {}
/** /**
* Constructs a <code>Stroker</code>. * Constructs a <code>Stroker</code>.
* *
* @param output an output <code>LineSink</code>. * @param output an output <code>LineSink</code>.
* @param lineWidth the desired line width in pixels, in S15.16 * @param lineWidth the desired line width in pixels
* format.
* @param capStyle the desired end cap style, one of * @param capStyle the desired end cap style, one of
* <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or * <code>CAP_BUTT</code>, <code>CAP_ROUND</code> or
* <code>CAP_SQUARE</code>. * <code>CAP_SQUARE</code>.
* @param joinStyle the desired line join style, one of * @param joinStyle the desired line join style, one of
* <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or * <code>JOIN_MITER</code>, <code>JOIN_ROUND</code> or
* <code>JOIN_BEVEL</code>. * <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 * @param transform a <code>Transform4</code> object indicating
* the transform that has been previously applied to all incoming * the transform that has been previously applied to all incoming
* coordinates. This is required in order to produce consistently * coordinates. This is required in order to produce consistently
* shaped end caps and joins. * shaped end caps and joins.
*/ */
public Stroker(LineSink output, public Stroker(LineSink output,
int lineWidth, float lineWidth,
int capStyle, int capStyle,
int joinStyle, int joinStyle,
int miterLimit, float miterLimit,
Transform4 transform) { float m00, float m01, float m10, float m11) {
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) {
this.output = output; this.output = output;
}
/** this.lineWidth2 = lineWidth / 2;
* Sets the parameters of this <code>Stroker</code>. this.scaledLineWidth2 = m00 * lineWidth2;
* @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.capStyle = capStyle; this.capStyle = capStyle;
this.joinStyle = joinStyle; this.joinStyle = joinStyle;
this.miterLimit = miterLimit;
this.transform = transform; m00_2_m01_2 = m00*m00 + m01*m01;
this.m00 = transform.m00; m10_2_m11_2 = m10*m10 + m11*m11;
this.m01 = transform.m01; m00_m10_m01_m11 = m00*m10 + m01*m11;
this.m10 = transform.m10;
this.m11 = transform.m11;
this.m00_2_m01_2 = (double)m00*m00 + (double)m01*m01; this.m00 = m00;
this.m10_2_m11_2 = (double)m10*m10 + (double)m11*m11; this.m01 = m01;
this.m00_m10_m01_m11 = (double)m00*m10 + (double)m01*m11; this.m10 = m10;
this.m11 = m11;
det = m00*m11 - m01*m10;
double dm00 = m00/65536.0; float limit = miterLimit * lineWidth2 * det;
double dm01 = m01/65536.0; this.miterLimitSq = limit*limit;
double dm10 = m10/65536.0;
double dm11 = m11/65536.0;
double determinant = dm00*dm11 - dm01*dm10;
if (joinStyle == JOIN_MITER) { this.numPenSegments = (int)(3.14159f * lineWidth);
double limit = this.pen_dx = new float[numPenSegments];
(miterLimit/65536.0)*(lineWidth2/65536.0)*determinant; this.pen_dy = new float[numPenSegments];
double limitSq = limit*limit; this.penIncluded = new boolean[numPenSegments];
this.miterLimitSq = (long)(limitSq*65536.0*65536.0); this.join = new float[2*numPenSegments];
}
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.penIncluded = new boolean[numPenSegments];
this.join = new int[2*numPenSegments];
}
for (int i = 0; i < numPenSegments; i++) { for (int i = 0; i < numPenSegments; i++) {
double r = lineWidth/2.0; double theta = (i * 2.0 * Math.PI)/numPenSegments;
double theta = (double)i*2.0*Math.PI/numPenSegments;
double cos = Math.cos(theta); double cos = Math.cos(theta);
double sin = Math.sin(theta); double sin = Math.sin(theta);
pen_dx[i] = (int)(r*(dm00*cos + dm01*sin)); pen_dx[i] = (float)(lineWidth2 * (m00*cos + m01*sin));
pen_dy[i] = (int)(r*(dm10*cos + dm11*sin)); pen_dy[i] = (float)(lineWidth2 * (m10*cos + m11*sin));
} }
prev = CLOSE; prev = CLOSE;
...@@ -233,32 +172,31 @@ public class Stroker extends LineSink { ...@@ -233,32 +172,31 @@ public class Stroker extends LineSink {
lineToOrigin = false; lineToOrigin = false;
} }
private void computeOffset(int x0, int y0, int x1, int y1, int[] m) { private void computeOffset(float x0, float y0,
long lx = (long)x1 - (long)x0; float x1, float y1, float[] m) {
long ly = (long)y1 - (long)y0; float lx = x1 - x0;
float ly = y1 - y0;
int dx, dy; float dx, dy;
if (m00 > 0 && m00 == m11 && m01 == 0 & m10 == 0) { 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) { if (ilen == 0) {
dx = dy = 0; dx = dy = 0;
} else { } else {
dx = (int)( (ly*scaledLineWidth2)/ilen); dx = (ly * scaledLineWidth2)/ilen;
dy = (int)(-(lx*scaledLineWidth2)/ilen); dy = -(lx * scaledLineWidth2)/ilen;
} }
} else { } else {
double dlx = x1 - x0;
double dly = y1 - y0;
double det = (double)m00*m11 - (double)m01*m10;
int sdet = (det > 0) ? 1 : -1; int sdet = (det > 0) ? 1 : -1;
double a = dly*m00 - dlx*m10; float a = ly * m00 - lx * m10;
double b = dly*m01 - dlx*m11; float b = ly * m01 - lx * m11;
double dh = PiscesMath.hypot(a, b); float dh = (float)Math.hypot(a, b);
double div = sdet*lineWidth2/(65536.0*dh); float div = sdet * lineWidth2/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; float ddx = ly * m00_2_m01_2 - lx * m00_m10_m01_m11;
dx = (int)(ddx*div); float ddy = ly * m00_m10_m01_m11 - lx * m10_2_m11_2;
dy = (int)(ddy*div); dx = ddx*div;
dy = ddy*div;
} }
m[0] = dx; m[0] = dx;
...@@ -267,58 +205,43 @@ public class Stroker extends LineSink { ...@@ -267,58 +205,43 @@ public class Stroker extends LineSink {
private void ensureCapacity(int newrindex) { private void ensureCapacity(int newrindex) {
if (reverse.length < newrindex) { if (reverse.length < newrindex) {
int[] tmp = new int[Math.max(newrindex, 6*reverse.length/5)]; reverse = java.util.Arrays.copyOf(reverse, 6*reverse.length/5);
System.arraycopy(reverse, 0, tmp, 0, rindex);
this.reverse = tmp;
} }
} }
private boolean isCCW(int x0, int y0, private boolean isCCW(float x0, float y0,
int x1, int y1, float x1, float y1,
int x2, int y2) { float x2, float y2) {
int dx0 = x1 - x0; return (x1 - x0) * (y2 - y1) < (y1 - y0) * (x2 - x1);
int dy0 = y1 - y0;
int dx1 = x2 - x1;
int dy1 = y2 - y1;
return (long)dx0*dy1 < (long)dy0*dx1;
} }
private boolean side(int x, int y, int x0, int y0, int x1, int y1) { private boolean side(float x, float y,
long lx = x; float x0, float y0,
long ly = y; float x1, float y1) {
long lx0 = x0; return (y0 - y1)*x + (x1 - x0)*y + (x0*y1 - x1*y0) > 0;
long ly0 = y0;
long lx1 = x1;
long ly1 = y1;
return (ly0 - ly1)*lx + (lx1 - lx0)*ly + (lx0*ly1 - lx1*ly0) > 0;
} }
private int computeRoundJoin(int cx, int cy, private int computeRoundJoin(float cx, float cy,
int xa, int ya, float xa, float ya,
int xb, int yb, float xb, float yb,
int side, int side,
boolean flip, boolean flip,
int[] join) { float[] join) {
int px, py; float px, py;
int ncoords = 0; int ncoords = 0;
boolean centerSide; boolean centerSide;
if (side == 0) { if (side == 0) {
centerSide = side(cx, cy, xa, ya, xb, yb); centerSide = side(cx, cy, xa, ya, xb, yb);
} else { } else {
centerSide = (side == 1) ? true : false; centerSide = (side == 1);
} }
for (int i = 0; i < numPenSegments; i++) { for (int i = 0; i < numPenSegments; i++) {
px = cx + pen_dx[i]; px = cx + pen_dx[i];
py = cy + pen_dy[i]; py = cy + pen_dy[i];
boolean penSide = side(px, py, xa, ya, xb, yb); boolean penSide = side(px, py, xa, ya, xb, yb);
if (penSide != centerSide) { penIncluded[i] = (penSide != centerSide);
penIncluded[i] = true;
} else {
penIncluded[i] = false;
}
} }
int start = -1, end = -1; int start = -1, end = -1;
...@@ -338,10 +261,10 @@ public class Stroker extends LineSink { ...@@ -338,10 +261,10 @@ public class Stroker extends LineSink {
} }
if (start != -1 && end != -1) { if (start != -1 && end != -1) {
long dxa = cx + pen_dx[start] - xa; float dxa = cx + pen_dx[start] - xa;
long dya = cy + pen_dy[start] - ya; float dya = cy + pen_dy[start] - ya;
long dxb = cx + pen_dx[start] - xb; float dxb = cx + pen_dx[start] - xb;
long dyb = cy + pen_dy[start] - yb; float dyb = cy + pen_dy[start] - yb;
boolean rev = (dxa*dxa + dya*dya > dxb*dxb + dyb*dyb); boolean rev = (dxa*dxa + dya*dya > dxb*dxb + dyb*dyb);
int i = rev ? end : start; int i = rev ? end : start;
...@@ -362,22 +285,25 @@ public class Stroker extends LineSink { ...@@ -362,22 +285,25 @@ public class Stroker extends LineSink {
return ncoords/2; return ncoords/2;
} }
private static final long ROUND_JOIN_THRESHOLD = 1000L; // pisces used to use fixed point arithmetic with 16 decimal digits. I
private static final long ROUND_JOIN_INTERNAL_THRESHOLD = 1000000000L; // 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, private void drawRoundJoin(float x, float y,
int omx, int omy, int mx, int my, float omx, float omy, float mx, float my,
int side, int side,
boolean flip, boolean flip,
boolean rev, boolean rev,
long threshold) { float threshold) {
if ((omx == 0 && omy == 0) || (mx == 0 && my == 0)) { if ((omx == 0 && omy == 0) || (mx == 0 && my == 0)) {
return; return;
} }
long domx = (long)omx - mx; float domx = omx - mx;
long domy = (long)omy - my; float domy = omy - my;
long len = domx*domx + domy*domy; float len = domx*domx + domy*domy;
if (len < threshold) { if (len < threshold) {
return; return;
} }
...@@ -389,10 +315,10 @@ public class Stroker extends LineSink { ...@@ -389,10 +315,10 @@ public class Stroker extends LineSink {
my = -my; my = -my;
} }
int bx0 = x + omx; float bx0 = x + omx;
int by0 = y + omy; float by0 = y + omy;
int bx1 = x + mx; float bx1 = x + mx;
int by1 = y + my; float by1 = y + my;
int npoints = computeRoundJoin(x, y, int npoints = computeRoundJoin(x, y,
bx0, by0, bx1, by1, side, flip, bx0, by0, bx1, by1, side, flip,
...@@ -404,40 +330,30 @@ public class Stroker extends LineSink { ...@@ -404,40 +330,30 @@ public class Stroker extends LineSink {
// Return the intersection point of the lines (ix0, iy0) -> (ix1, iy1) // Return the intersection point of the lines (ix0, iy0) -> (ix1, iy1)
// and (ix0p, iy0p) -> (ix1p, iy1p) in m[0] and m[1] // and (ix0p, iy0p) -> (ix1p, iy1p) in m[0] and m[1]
private void computeMiter(int ix0, int iy0, int ix1, int iy1, private void computeMiter(float x0, float y0, float x1, float y1,
int ix0p, int iy0p, int ix1p, int iy1p, float x0p, float y0p, float x1p, float y1p,
int[] m) { float[] m) {
long x0 = ix0; float x10 = x1 - x0;
long y0 = iy0; float y10 = y1 - y0;
long x1 = ix1; float x10p = x1p - x0p;
long y1 = iy1; float y10p = y1p - y0p;
long x0p = ix0p; float den = x10*y10p - x10p*y10;
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;
if (den == 0) { if (den == 0) {
m[0] = ix0; m[0] = x0;
m[1] = iy0; m[1] = y0;
return; return;
} }
long t = (x1p*(y0 - y0p) - x0*y10p + x0p*(y1p - y0)) >> 16; float t = x1p*(y0 - y0p) - x0*y10p + x0p*(y1p - y0);
m[0] = (int)(x0 + (t*x10)/den); m[0] = x0 + (t*x10)/den;
m[1] = (int)(y0 + (t*y10)/den); m[1] = y0 + (t*y10)/den;
} }
private void drawMiter(int px0, int py0, private void drawMiter(float px0, float py0,
int x0, int y0, float x0, float y0,
int x1, int y1, float x1, float y1,
int omx, int omy, int mx, int my, float omx, float omy, float mx, float my,
boolean rev) { boolean rev) {
if (mx == omx && my == omy) { if (mx == omx && my == omy) {
return; return;
...@@ -461,11 +377,11 @@ public class Stroker extends LineSink { ...@@ -461,11 +377,11 @@ public class Stroker extends LineSink {
miter); miter);
// Compute miter length in untransformed coordinates // Compute miter length in untransformed coordinates
long dx = (long)miter[0] - x0; float dx = miter[0] - x0;
long dy = (long)miter[1] - y0; float dy = miter[1] - y0;
long a = (dy*m00 - dx*m10) >> 16; float a = dy*m00 - dx*m10;
long b = (dy*m01 - dx*m11) >> 16; float b = dy*m01 - dx*m11;
long lenSq = a*a + b*b; float lenSq = a*a + b*b;
if (lenSq < miterLimitSq) { if (lenSq < miterLimitSq) {
emitLineTo(miter[0], miter[1], rev); emitLineTo(miter[0], miter[1], rev);
...@@ -473,7 +389,7 @@ public class Stroker extends LineSink { ...@@ -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 + ")"); // System.out.println("Stroker.moveTo(" + x0/65536.0 + ", " + y0/65536.0 + ")");
if (lineToOrigin) { if (lineToOrigin) {
...@@ -501,7 +417,7 @@ public class Stroker extends LineSink { ...@@ -501,7 +417,7 @@ public class Stroker extends LineSink {
this.joinSegment = true; 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 + ")"); // System.out.println("Stroker.lineTo(" + x1/65536.0 + ", " + y1/65536.0 + ")");
if (lineToOrigin) { if (lineToOrigin) {
...@@ -526,10 +442,10 @@ public class Stroker extends LineSink { ...@@ -526,10 +442,10 @@ public class Stroker extends LineSink {
joinSegment = false; 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); computeOffset(x0, y0, x1, y1, offset);
int mx = offset[0]; float mx = offset[0];
int my = offset[1]; float my = offset[1];
if (!started) { if (!started) {
emitMoveTo(x0 + mx, y0 + my); emitMoveTo(x0 + mx, y0 + my);
...@@ -567,10 +483,6 @@ public class Stroker extends LineSink { ...@@ -567,10 +483,6 @@ public class Stroker extends LineSink {
emitLineTo(x0 - mx, y0 - my, true); emitLineTo(x0 - mx, y0 - my, true);
emitLineTo(x1 - mx, y1 - 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.omx = mx;
this.omy = my; this.omy = my;
this.px0 = x0; this.px0 = x0;
...@@ -594,8 +506,8 @@ public class Stroker extends LineSink { ...@@ -594,8 +506,8 @@ public class Stroker extends LineSink {
} }
computeOffset(x0, y0, sx0, sy0, offset); computeOffset(x0, y0, sx0, sy0, offset);
int mx = offset[0]; float mx = offset[0];
int my = offset[1]; float my = offset[1];
// Draw penultimate join // Draw penultimate join
boolean ccw = isCCW(px0, py0, x0, y0, sx0, sy0); boolean ccw = isCCW(px0, py0, x0, y0, sx0, sy0);
...@@ -678,12 +590,10 @@ public class Stroker extends LineSink { ...@@ -678,12 +590,10 @@ public class Stroker extends LineSink {
this.prev = MOVE_TO; this.prev = MOVE_TO;
} }
long lineLength(long ldx, long ldy) { double userSpaceLineLength(double dx, double dy) {
long ldet = ((long)m00*m11 - (long)m01*m10) >> 16; double a = (dy*m00 - dx*m10)/det;
long la = ((long)ldy*m00 - (long)ldx*m10)/ldet; double b = (dy*m01 - dx*m11)/det;
long lb = ((long)ldy*m01 - (long)ldx*m11)/ldet; return Math.hypot(a, b);
long llen = (int)PiscesMath.hypot(la, lb);
return llen;
} }
private void finish() { private void finish() {
...@@ -692,13 +602,13 @@ public class Stroker extends LineSink { ...@@ -692,13 +602,13 @@ public class Stroker extends LineSink {
omx, omy, -omx, -omy, 1, false, false, omx, omy, -omx, -omy, 1, false, false,
ROUND_JOIN_THRESHOLD); ROUND_JOIN_THRESHOLD);
} else if (capStyle == CAP_SQUARE) { } else if (capStyle == CAP_SQUARE) {
long ldx = (long)(px0 - x0); float dx = px0 - x0;
long ldy = (long)(py0 - y0); float dy = py0 - y0;
long llen = lineLength(ldx, ldy); float len = (float)userSpaceLineLength(dx, dy);
long s = (long)lineWidth2*65536/llen; float s = lineWidth2/len;
int capx = x0 - (int)(ldx*s >> 16); float capx = x0 - dx*s;
int capy = y0 - (int)(ldy*s >> 16); float capy = y0 - dy*s;
emitLineTo(capx + omx, capy + omy); emitLineTo(capx + omx, capy + omy);
emitLineTo(capx - omx, capy - omy); emitLineTo(capx - omx, capy - omy);
...@@ -714,13 +624,13 @@ public class Stroker extends LineSink { ...@@ -714,13 +624,13 @@ public class Stroker extends LineSink {
-mx0, -my0, mx0, my0, 1, false, false, -mx0, -my0, mx0, my0, 1, false, false,
ROUND_JOIN_THRESHOLD); ROUND_JOIN_THRESHOLD);
} else if (capStyle == CAP_SQUARE) { } else if (capStyle == CAP_SQUARE) {
long ldx = (long)(sx1 - sx0); float dx = sx1 - sx0;
long ldy = (long)(sy1 - sy0); float dy = sy1 - sy0;
long llen = lineLength(ldx, ldy); float len = (float)userSpaceLineLength(dx, dy);
long s = (long)lineWidth2*65536/llen; float s = lineWidth2/len;
int capx = sx0 - (int)(ldx*s >> 16); float capx = sx0 - dx*s;
int capy = sy0 - (int)(ldy*s >> 16); float capy = sy0 - dy*s;
emitLineTo(capx - mx0, capy - my0); emitLineTo(capx - mx0, capy - my0);
emitLineTo(capx + mx0, capy + my0); emitLineTo(capx + mx0, capy + my0);
...@@ -730,17 +640,17 @@ public class Stroker extends LineSink { ...@@ -730,17 +640,17 @@ public class Stroker extends LineSink {
this.joinSegment = false; 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 + ")"); // System.out.println("Stroker.emitMoveTo(" + x0/65536.0 + ", " + y0/65536.0 + ")");
output.moveTo(x0, y0); 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 + ")"); // System.out.println("Stroker.emitLineTo(" + x0/65536.0 + ", " + y0/65536.0 + ")");
output.lineTo(x1, y1); output.lineTo(x1, y1);
} }
private void emitLineTo(int x1, int y1, boolean rev) { private void emitLineTo(float x1, float y1, boolean rev) {
if (rev) { if (rev) {
ensureCapacity(rindex + 2); ensureCapacity(rindex + 2);
reverse[rindex++] = x1; reverse[rindex++] = x1;
...@@ -755,3 +665,4 @@ public class Stroker extends LineSink { ...@@ -755,3 +665,4 @@ public class Stroker extends LineSink {
output.close(); 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.
先完成此消息的编辑!
想要评论请 注册