8198885: upgrade Marlin (java2d) to 0.9.1

Clipping implemented in Dasher (curve subdivision at clip edges) + higher quality(curve, subpixels) + new path simplifier

Reviewed-by: prr, serb
This commit is contained in:
Laurent Bourgès 2018-03-27 22:09:43 +02:00
parent 7efc35390e
commit 385ad9e160
35 changed files with 4070 additions and 1657 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2018, 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
@ -99,7 +99,7 @@ final class ByteArrayCache implements MarlinConst {
Reference(final ByteArrayCache cache, final int initialSize) {
this.cache = cache;
this.clean = cache.clean;
this.initial = createArray(initialSize, clean);
this.initial = createArray(initialSize);
if (DO_STATS) {
cache.stats.totalInitial += initialSize;
}
@ -116,7 +116,7 @@ final class ByteArrayCache implements MarlinConst {
logInfo(getLogPrefix(clean) + "ByteArrayCache: "
+ "getArray[oversize]: length=\t" + length);
}
return createArray(length, clean);
return createArray(length);
}
byte[] widenArray(final byte[] array, final int usedSize,
@ -202,7 +202,7 @@ final class ByteArrayCache implements MarlinConst {
if (DO_STATS) {
stats.createOp++;
}
return createArray(arraySize, clean);
return createArray(arraySize);
}
void putArray(final byte[] array)
@ -229,12 +229,8 @@ final class ByteArrayCache implements MarlinConst {
}
}
static byte[] createArray(final int length, final boolean clean) {
if (clean) {
return new byte[length];
}
// use JDK9 Unsafe.allocateUninitializedArray(class, length):
return (byte[]) OffHeapArray.UNSAFE.allocateUninitializedArray(byte.class, length);
static byte[] createArray(final int length) {
return new byte[length];
}
static void fill(final byte[] array, final int fromIndex,

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2018, 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
@ -33,86 +33,94 @@ final class Curve {
Curve() {
}
void set(float[] points, int type) {
switch(type) {
case 8:
void set(final float[] points, final int type) {
// if instead of switch (perf + most probable cases first)
if (type == 8) {
set(points[0], points[1],
points[2], points[3],
points[4], points[5],
points[6], points[7]);
return;
case 6:
} else if (type == 4) {
set(points[0], points[1],
points[2], points[3]);
} else {
set(points[0], points[1],
points[2], points[3],
points[4], points[5]);
return;
default:
throw new InternalError("Curves can only be cubic or quadratic");
}
}
void set(float x1, float y1,
float x2, float y2,
float x3, float y3,
float x4, float y4)
void set(final float x1, final float y1,
final float x2, final float y2,
final float x3, final float y3,
final float x4, final float y4)
{
final float dx32 = 3.0f * (x3 - x2);
final float dy32 = 3.0f * (y3 - y2);
final float dx21 = 3.0f * (x2 - x1);
final float dy21 = 3.0f * (y2 - y1);
ax = (x4 - x1) - dx32;
ax = (x4 - x1) - dx32; // A = P3 - P0 - 3 (P2 - P1) = (P3 - P0) + 3 (P1 - P2)
ay = (y4 - y1) - dy32;
bx = (dx32 - dx21);
bx = (dx32 - dx21); // B = 3 (P2 - P1) - 3(P1 - P0) = 3 (P2 + P0) - 6 P1
by = (dy32 - dy21);
cx = dx21;
cx = dx21; // C = 3 (P1 - P0)
cy = dy21;
dx = x1;
dx = x1; // D = P0
dy = y1;
dax = 3.0f * ax; day = 3.0f * ay;
dbx = 2.0f * bx; dby = 2.0f * by;
dax = 3.0f * ax;
day = 3.0f * ay;
dbx = 2.0f * bx;
dby = 2.0f * by;
}
void set(float x1, float y1,
float x2, float y2,
float x3, float y3)
void set(final float x1, final float y1,
final float x2, final float y2,
final float x3, final float y3)
{
final float dx21 = (x2 - x1);
final float dy21 = (y2 - y1);
ax = 0.0f; ay = 0.0f;
bx = (x3 - x2) - dx21;
ax = 0.0f; // A = 0
ay = 0.0f;
bx = (x3 - x2) - dx21; // B = P3 - P0 - 2 P2
by = (y3 - y2) - dy21;
cx = 2.0f * dx21;
cx = 2.0f * dx21; // C = 2 (P2 - P1)
cy = 2.0f * dy21;
dx = x1;
dx = x1; // D = P1
dy = y1;
dax = 0.0f; day = 0.0f;
dbx = 2.0f * bx; dby = 2.0f * by;
dax = 0.0f;
day = 0.0f;
dbx = 2.0f * bx;
dby = 2.0f * by;
}
float xat(float t) {
return t * (t * (t * ax + bx) + cx) + dx;
}
float yat(float t) {
return t * (t * (t * ay + by) + cy) + dy;
void set(final float x1, final float y1,
final float x2, final float y2)
{
final float dx21 = (x2 - x1);
final float dy21 = (y2 - y1);
ax = 0.0f; // A = 0
ay = 0.0f;
bx = 0.0f; // B = 0
by = 0.0f;
cx = dx21; // C = (P2 - P1)
cy = dy21;
dx = x1; // D = P1
dy = y1;
dax = 0.0f;
day = 0.0f;
dbx = 0.0f;
dby = 0.0f;
}
float dxat(float t) {
return t * (t * dax + dbx) + cx;
}
float dyat(float t) {
return t * (t * day + dby) + cy;
}
int dxRoots(float[] roots, int off) {
int dxRoots(final float[] roots, final int off) {
return Helpers.quadraticRoots(dax, dbx, cx, roots, off);
}
int dyRoots(float[] roots, int off) {
int dyRoots(final float[] roots, final int off) {
return Helpers.quadraticRoots(day, dby, cy, roots, off);
}
int infPoints(float[] pts, int off) {
int infPoints(final float[] pts, final int off) {
// inflection point at t if -f'(t)x*f''(t)y + f'(t)y*f''(t)x == 0
// Fortunately, this turns out to be quadratic, so there are at
// most 2 inflection points.
@ -123,19 +131,30 @@ final class Curve {
return Helpers.quadraticRoots(a, b, c, pts, off);
}
int xPoints(final float[] ts, final int off, final float x)
{
return Helpers.cubicRootsInAB(ax, bx, cx, dx - x, ts, off, 0.0f, 1.0f);
}
int yPoints(final float[] ts, final int off, final float y)
{
return Helpers.cubicRootsInAB(ay, by, cy, dy - y, ts, off, 0.0f, 1.0f);
}
// finds points where the first and second derivative are
// perpendicular. This happens when g(t) = f'(t)*f''(t) == 0 (where
// * is a dot product). Unfortunately, we have to solve a cubic.
private int perpendiculardfddf(float[] pts, int off) {
private int perpendiculardfddf(final float[] pts, final int off) {
assert pts.length >= off + 4;
// these are the coefficients of some multiple of g(t) (not g(t),
// because the roots of a polynomial are not changed after multiplication
// by a constant, and this way we save a few multiplications).
final float a = 2.0f * (dax*dax + day*day);
final float b = 3.0f * (dax*dbx + day*dby);
final float c = 2.0f * (dax*cx + day*cy) + dbx*dbx + dby*dby;
final float d = dbx*cx + dby*cy;
final float a = 2.0f * (dax * dax + day * day);
final float b = 3.0f * (dax * dbx + day * dby);
final float c = 2.0f * (dax * cx + day * cy) + dbx * dbx + dby * dby;
final float d = dbx * cx + dby * cy;
return Helpers.cubicRootsInAB(a, b, c, d, pts, off, 0.0f, 1.0f);
}
@ -152,22 +171,24 @@ final class Curve {
// at most 4 sub-intervals of (0,1). ROC has asymptotes at inflection
// points, so roc-w can have at least 6 roots. This shouldn't be a
// problem for what we're trying to do (draw a nice looking curve).
int rootsOfROCMinusW(float[] roots, int off, final float w, final float err) {
int rootsOfROCMinusW(final float[] roots, final int off, final float w2, final float err) {
// no OOB exception, because by now off<=6, and roots.length >= 10
assert off <= 6 && roots.length >= 10;
int ret = off;
int numPerpdfddf = perpendiculardfddf(roots, off);
float t0 = 0.0f, ft0 = ROCsq(t0) - w*w;
roots[off + numPerpdfddf] = 1.0f; // always check interval end points
numPerpdfddf++;
for (int i = off; i < off + numPerpdfddf; i++) {
float t1 = roots[i], ft1 = ROCsq(t1) - w*w;
final int end = off + perpendiculardfddf(roots, off);
roots[end] = 1.0f; // always check interval end points
float t0 = 0.0f, ft0 = ROCsq(t0) - w2;
for (int i = off; i <= end; i++) {
float t1 = roots[i], ft1 = ROCsq(t1) - w2;
if (ft0 == 0.0f) {
roots[ret++] = t0;
} else if (ft1 * ft0 < 0.0f) { // have opposite signs
// (ROC(t)^2 == w^2) == (ROC(t) == w) is true because
// ROC(t) >= 0 for all t.
roots[ret++] = falsePositionROCsqMinusX(t0, t1, w*w, err);
roots[ret++] = falsePositionROCsqMinusX(t0, t1, w2, err);
}
t0 = t1;
ft0 = ft1;
@ -176,9 +197,9 @@ final class Curve {
return ret - off;
}
private static float eliminateInf(float x) {
private static float eliminateInf(final float x) {
return (x == Float.POSITIVE_INFINITY ? Float.MAX_VALUE :
(x == Float.NEGATIVE_INFINITY ? Float.MIN_VALUE : x));
(x == Float.NEGATIVE_INFINITY ? Float.MIN_VALUE : x));
}
// A slight modification of the false position algorithm on wikipedia.
@ -188,17 +209,18 @@ final class Curve {
// expressions make it into the language), depending on how closures
// and turn out. Same goes for the newton's method
// algorithm in Helpers.java
private float falsePositionROCsqMinusX(float x0, float x1,
final float x, final float err)
private float falsePositionROCsqMinusX(final float t0, final float t1,
final float w2, final float err)
{
final int iterLimit = 100;
int side = 0;
float t = x1, ft = eliminateInf(ROCsq(t) - x);
float s = x0, fs = eliminateInf(ROCsq(s) - x);
float t = t1, ft = eliminateInf(ROCsq(t) - w2);
float s = t0, fs = eliminateInf(ROCsq(s) - w2);
float r = s, fr;
for (int i = 0; i < iterLimit && Math.abs(t - s) > err * Math.abs(t + s); i++) {
r = (fs * t - ft * s) / (fs - ft);
fr = ROCsq(r) - x;
fr = ROCsq(r) - w2;
if (sameSign(fr, ft)) {
ft = fr; t = r;
if (side < 0) {
@ -207,7 +229,7 @@ final class Curve {
} else {
side = -1;
}
} else if (fr * fs > 0) {
} else if (fr * fs > 0.0f) {
fs = fr; s = r;
if (side > 0) {
ft /= (1 << side);
@ -222,7 +244,7 @@ final class Curve {
return r;
}
private static boolean sameSign(float x, float y) {
private static boolean sameSign(final float x, final float y) {
// another way is to test if x*y > 0. This is bad for small x, y.
return (x < 0.0f && y < 0.0f) || (x > 0.0f && y > 0.0f);
}
@ -230,14 +252,13 @@ final class Curve {
// returns the radius of curvature squared at t of this curve
// see http://en.wikipedia.org/wiki/Radius_of_curvature_(applications)
private float ROCsq(final float t) {
// dx=xat(t) and dy=yat(t). These calls have been inlined for efficiency
final float dx = t * (t * dax + dbx) + cx;
final float dy = t * (t * day + dby) + cy;
final float ddx = 2.0f * dax * t + dbx;
final float ddy = 2.0f * day * t + dby;
final float dx2dy2 = dx*dx + dy*dy;
final float ddx2ddy2 = ddx*ddx + ddy*ddy;
final float ddxdxddydy = ddx*dx + ddy*dy;
return dx2dy2*((dx2dy2*dx2dy2) / (dx2dy2 * ddx2ddy2 - ddxdxddydy*ddxdxddydy));
final float dx2dy2 = dx * dx + dy * dy;
final float ddx2ddy2 = ddx * ddx + ddy * ddy;
final float ddxdxddydy = ddx * dx + ddy * dy;
return dx2dy2 * ((dx2dy2 * dx2dy2) / (dx2dy2 * ddx2ddy2 - ddxdxddydy * ddxdxddydy));
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2018, 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
@ -33,86 +33,94 @@ final class DCurve {
DCurve() {
}
void set(double[] points, int type) {
switch(type) {
case 8:
void set(final double[] points, final int type) {
// if instead of switch (perf + most probable cases first)
if (type == 8) {
set(points[0], points[1],
points[2], points[3],
points[4], points[5],
points[6], points[7]);
return;
case 6:
} else if (type == 4) {
set(points[0], points[1],
points[2], points[3]);
} else {
set(points[0], points[1],
points[2], points[3],
points[4], points[5]);
return;
default:
throw new InternalError("Curves can only be cubic or quadratic");
}
}
void set(double x1, double y1,
double x2, double y2,
double x3, double y3,
double x4, double y4)
void set(final double x1, final double y1,
final double x2, final double y2,
final double x3, final double y3,
final double x4, final double y4)
{
final double dx32 = 3.0d * (x3 - x2);
final double dy32 = 3.0d * (y3 - y2);
final double dx21 = 3.0d * (x2 - x1);
final double dy21 = 3.0d * (y2 - y1);
ax = (x4 - x1) - dx32;
ax = (x4 - x1) - dx32; // A = P3 - P0 - 3 (P2 - P1) = (P3 - P0) + 3 (P1 - P2)
ay = (y4 - y1) - dy32;
bx = (dx32 - dx21);
bx = (dx32 - dx21); // B = 3 (P2 - P1) - 3(P1 - P0) = 3 (P2 + P0) - 6 P1
by = (dy32 - dy21);
cx = dx21;
cx = dx21; // C = 3 (P1 - P0)
cy = dy21;
dx = x1;
dx = x1; // D = P0
dy = y1;
dax = 3.0d * ax; day = 3.0d * ay;
dbx = 2.0d * bx; dby = 2.0d * by;
dax = 3.0d * ax;
day = 3.0d * ay;
dbx = 2.0d * bx;
dby = 2.0d * by;
}
void set(double x1, double y1,
double x2, double y2,
double x3, double y3)
void set(final double x1, final double y1,
final double x2, final double y2,
final double x3, final double y3)
{
final double dx21 = (x2 - x1);
final double dy21 = (y2 - y1);
ax = 0.0d; ay = 0.0d;
bx = (x3 - x2) - dx21;
ax = 0.0d; // A = 0
ay = 0.0d;
bx = (x3 - x2) - dx21; // B = P3 - P0 - 2 P2
by = (y3 - y2) - dy21;
cx = 2.0d * dx21;
cx = 2.0d * dx21; // C = 2 (P2 - P1)
cy = 2.0d * dy21;
dx = x1;
dx = x1; // D = P1
dy = y1;
dax = 0.0d; day = 0.0d;
dbx = 2.0d * bx; dby = 2.0d * by;
dax = 0.0d;
day = 0.0d;
dbx = 2.0d * bx;
dby = 2.0d * by;
}
double xat(double t) {
return t * (t * (t * ax + bx) + cx) + dx;
}
double yat(double t) {
return t * (t * (t * ay + by) + cy) + dy;
void set(final double x1, final double y1,
final double x2, final double y2)
{
final double dx21 = (x2 - x1);
final double dy21 = (y2 - y1);
ax = 0.0d; // A = 0
ay = 0.0d;
bx = 0.0d; // B = 0
by = 0.0d;
cx = dx21; // C = (P2 - P1)
cy = dy21;
dx = x1; // D = P1
dy = y1;
dax = 0.0d;
day = 0.0d;
dbx = 0.0d;
dby = 0.0d;
}
double dxat(double t) {
return t * (t * dax + dbx) + cx;
}
double dyat(double t) {
return t * (t * day + dby) + cy;
}
int dxRoots(double[] roots, int off) {
int dxRoots(final double[] roots, final int off) {
return DHelpers.quadraticRoots(dax, dbx, cx, roots, off);
}
int dyRoots(double[] roots, int off) {
int dyRoots(final double[] roots, final int off) {
return DHelpers.quadraticRoots(day, dby, cy, roots, off);
}
int infPoints(double[] pts, int off) {
int infPoints(final double[] pts, final int off) {
// inflection point at t if -f'(t)x*f''(t)y + f'(t)y*f''(t)x == 0
// Fortunately, this turns out to be quadratic, so there are at
// most 2 inflection points.
@ -123,19 +131,30 @@ final class DCurve {
return DHelpers.quadraticRoots(a, b, c, pts, off);
}
int xPoints(final double[] ts, final int off, final double x)
{
return DHelpers.cubicRootsInAB(ax, bx, cx, dx - x, ts, off, 0.0d, 1.0d);
}
int yPoints(final double[] ts, final int off, final double y)
{
return DHelpers.cubicRootsInAB(ay, by, cy, dy - y, ts, off, 0.0d, 1.0d);
}
// finds points where the first and second derivative are
// perpendicular. This happens when g(t) = f'(t)*f''(t) == 0 (where
// * is a dot product). Unfortunately, we have to solve a cubic.
private int perpendiculardfddf(double[] pts, int off) {
private int perpendiculardfddf(final double[] pts, final int off) {
assert pts.length >= off + 4;
// these are the coefficients of some multiple of g(t) (not g(t),
// because the roots of a polynomial are not changed after multiplication
// by a constant, and this way we save a few multiplications).
final double a = 2.0d * (dax*dax + day*day);
final double b = 3.0d * (dax*dbx + day*dby);
final double c = 2.0d * (dax*cx + day*cy) + dbx*dbx + dby*dby;
final double d = dbx*cx + dby*cy;
final double a = 2.0d * (dax * dax + day * day);
final double b = 3.0d * (dax * dbx + day * dby);
final double c = 2.0d * (dax * cx + day * cy) + dbx * dbx + dby * dby;
final double d = dbx * cx + dby * cy;
return DHelpers.cubicRootsInAB(a, b, c, d, pts, off, 0.0d, 1.0d);
}
@ -152,22 +171,24 @@ final class DCurve {
// at most 4 sub-intervals of (0,1). ROC has asymptotes at inflection
// points, so roc-w can have at least 6 roots. This shouldn't be a
// problem for what we're trying to do (draw a nice looking curve).
int rootsOfROCMinusW(double[] roots, int off, final double w, final double err) {
int rootsOfROCMinusW(final double[] roots, final int off, final double w2, final double err) {
// no OOB exception, because by now off<=6, and roots.length >= 10
assert off <= 6 && roots.length >= 10;
int ret = off;
int numPerpdfddf = perpendiculardfddf(roots, off);
double t0 = 0.0d, ft0 = ROCsq(t0) - w*w;
roots[off + numPerpdfddf] = 1.0d; // always check interval end points
numPerpdfddf++;
for (int i = off; i < off + numPerpdfddf; i++) {
double t1 = roots[i], ft1 = ROCsq(t1) - w*w;
final int end = off + perpendiculardfddf(roots, off);
roots[end] = 1.0d; // always check interval end points
double t0 = 0.0d, ft0 = ROCsq(t0) - w2;
for (int i = off; i <= end; i++) {
double t1 = roots[i], ft1 = ROCsq(t1) - w2;
if (ft0 == 0.0d) {
roots[ret++] = t0;
} else if (ft1 * ft0 < 0.0d) { // have opposite signs
// (ROC(t)^2 == w^2) == (ROC(t) == w) is true because
// ROC(t) >= 0 for all t.
roots[ret++] = falsePositionROCsqMinusX(t0, t1, w*w, err);
roots[ret++] = falsePositionROCsqMinusX(t0, t1, w2, err);
}
t0 = t1;
ft0 = ft1;
@ -176,9 +197,9 @@ final class DCurve {
return ret - off;
}
private static double eliminateInf(double x) {
private static double eliminateInf(final double x) {
return (x == Double.POSITIVE_INFINITY ? Double.MAX_VALUE :
(x == Double.NEGATIVE_INFINITY ? Double.MIN_VALUE : x));
(x == Double.NEGATIVE_INFINITY ? Double.MIN_VALUE : x));
}
// A slight modification of the false position algorithm on wikipedia.
@ -188,17 +209,18 @@ final class DCurve {
// expressions make it into the language), depending on how closures
// and turn out. Same goes for the newton's method
// algorithm in DHelpers.java
private double falsePositionROCsqMinusX(double x0, double x1,
final double x, final double err)
private double falsePositionROCsqMinusX(final double t0, final double t1,
final double w2, final double err)
{
final int iterLimit = 100;
int side = 0;
double t = x1, ft = eliminateInf(ROCsq(t) - x);
double s = x0, fs = eliminateInf(ROCsq(s) - x);
double t = t1, ft = eliminateInf(ROCsq(t) - w2);
double s = t0, fs = eliminateInf(ROCsq(s) - w2);
double r = s, fr;
for (int i = 0; i < iterLimit && Math.abs(t - s) > err * Math.abs(t + s); i++) {
r = (fs * t - ft * s) / (fs - ft);
fr = ROCsq(r) - x;
fr = ROCsq(r) - w2;
if (sameSign(fr, ft)) {
ft = fr; t = r;
if (side < 0) {
@ -207,7 +229,7 @@ final class DCurve {
} else {
side = -1;
}
} else if (fr * fs > 0) {
} else if (fr * fs > 0.0d) {
fs = fr; s = r;
if (side > 0) {
ft /= (1 << side);
@ -222,7 +244,7 @@ final class DCurve {
return r;
}
private static boolean sameSign(double x, double y) {
private static boolean sameSign(final double x, final double y) {
// another way is to test if x*y > 0. This is bad for small x, y.
return (x < 0.0d && y < 0.0d) || (x > 0.0d && y > 0.0d);
}
@ -230,14 +252,13 @@ final class DCurve {
// returns the radius of curvature squared at t of this curve
// see http://en.wikipedia.org/wiki/Radius_of_curvature_(applications)
private double ROCsq(final double t) {
// dx=xat(t) and dy=yat(t). These calls have been inlined for efficiency
final double dx = t * (t * dax + dbx) + cx;
final double dy = t * (t * day + dby) + cy;
final double ddx = 2.0d * dax * t + dbx;
final double ddy = 2.0d * day * t + dby;
final double dx2dy2 = dx*dx + dy*dy;
final double ddx2ddy2 = ddx*ddx + ddy*ddy;
final double ddxdxddydy = ddx*dx + ddy*dy;
return dx2dy2*((dx2dy2*dx2dy2) / (dx2dy2 * ddx2ddy2 - ddxdxddydy*ddxdxddydy));
final double dx2dy2 = dx * dx + dy * dy;
final double ddx2ddy2 = ddx * ddx + ddy * ddy;
final double ddxdxddydy = ddx * dx + ddy * dy;
return dx2dy2 * ((dx2dy2 * dx2dy2) / (dx2dy2 * ddx2ddy2 - ddxdxddydy * ddxdxddydy));
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2018, 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
@ -26,6 +26,8 @@
package sun.java2d.marlin;
import java.util.Arrays;
import sun.java2d.marlin.DTransformingPathConsumer2D.CurveBasicMonotonizer;
import sun.java2d.marlin.DTransformingPathConsumer2D.CurveClipSplitter;
/**
* The <code>DDasher</code> class takes a series of linear commands
@ -40,8 +42,9 @@ import java.util.Arrays;
*/
final class DDasher implements DPathConsumer2D, MarlinConst {
static final int REC_LIMIT = 4;
static final double ERR = 0.01d;
/* huge circle with radius ~ 2E9 only needs 12 subdivision levels */
static final int REC_LIMIT = 16;
static final double CURVE_LEN_ERR = MarlinProperties.getCurveLengthError(); // 0.01 initial
static final double MIN_T_INC = 1.0d / (1 << REC_LIMIT);
// More than 24 bits of mantissa means we can no longer accurately
@ -63,8 +66,10 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
private boolean dashOn;
private double phase;
private double sx, sy;
private double x0, y0;
// The starting point of the path
private double sx0, sy0;
// the current point
private double cx0, cy0;
// temporary storage for the current curve
private final double[] curCurvepts;
@ -75,11 +80,34 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
// flag to recycle dash array copy
boolean recycleDashes;
// We don't emit the first dash right away. If we did, caps would be
// drawn on it, but we need joins to be drawn if there's a closePath()
// So, we store the path elements that make up the first dash in the
// buffer below.
private double[] firstSegmentsBuffer; // dynamic array
private int firstSegidx;
// dashes ref (dirty)
final DoubleArrayCache.Reference dashes_ref;
// firstSegmentsBuffer ref (dirty)
final DoubleArrayCache.Reference firstSegmentsBuffer_ref;
// Bounds of the drawing region, at pixel precision.
private double[] clipRect;
// the outcode of the current point
private int cOutCode = 0;
private boolean subdivide = DO_CLIP_SUBDIVIDER;
private final LengthIterator li = new LengthIterator();
private final CurveClipSplitter curveSplitter;
private double cycleLen;
private boolean outside;
private double totalSkipLen;
/**
* Constructs a <code>DDasher</code>.
* @param rdrCtx per-thread renderer context
@ -95,6 +123,8 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
// we need curCurvepts to be able to contain 2 curves because when
// dashing curves, we need to subdivide it
curCurvepts = new double[8 * 2];
this.curveSplitter = rdrCtx.curveClipSplitter;
}
/**
@ -115,10 +145,13 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
// Normalize so 0 <= phase < dash[0]
int sidx = 0;
dashOn = true;
double sum = 0.0d;
for (double d : dash) {
sum += d;
}
this.cycleLen = sum;
double cycles = phase / sum;
if (phase < 0.0d) {
if (-cycles >= MAX_CYCLES) {
@ -167,6 +200,12 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
this.recycleDashes = recycleDashes;
if (rdrCtx.doClip) {
this.clipRect = rdrCtx.clipRect;
} else {
this.clipRect = null;
this.cOutCode = 0;
}
return this; // fluent API
}
@ -204,33 +243,42 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
@Override
public void moveTo(final double x0, final double y0) {
if (firstSegidx != 0) {
out.moveTo(sx, sy);
out.moveTo(sx0, sy0);
emitFirstSegments();
}
needsMoveTo = true;
this.needsMoveTo = true;
this.idx = startIdx;
this.dashOn = this.startDashOn;
this.phase = this.startPhase;
this.sx = x0;
this.sy = y0;
this.x0 = x0;
this.y0 = y0;
this.cx0 = x0;
this.cy0 = y0;
// update starting point:
this.sx0 = x0;
this.sy0 = y0;
this.starting = true;
if (clipRect != null) {
final int outcode = DHelpers.outcode(x0, y0, clipRect);
this.cOutCode = outcode;
this.outside = false;
this.totalSkipLen = 0.0d;
}
}
private void emitSeg(double[] buf, int off, int type) {
switch (type) {
case 8:
out.curveTo(buf[off+0], buf[off+1],
buf[off+2], buf[off+3],
buf[off+4], buf[off+5]);
out.curveTo(buf[off ], buf[off + 1],
buf[off + 2], buf[off + 3],
buf[off + 4], buf[off + 5]);
return;
case 6:
out.quadTo(buf[off+0], buf[off+1],
buf[off+2], buf[off+3]);
out.quadTo(buf[off ], buf[off + 1],
buf[off + 2], buf[off + 3]);
return;
case 4:
out.lineTo(buf[off], buf[off+1]);
out.lineTo(buf[off], buf[off + 1]);
return;
default:
}
@ -246,12 +294,6 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
}
firstSegidx = 0;
}
// We don't emit the first dash right away. If we did, caps would be
// drawn on it, but we need joins to be drawn if there's a closePath()
// So, we store the path elements that make up the first dash in the
// buffer below.
private double[] firstSegmentsBuffer; // dynamic array
private int firstSegidx;
// precondition: pts must be in relative coordinates (relative to x0,y0)
private void goTo(final double[] pts, final int off, final int type,
@ -267,7 +309,7 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
} else {
if (needsMoveTo) {
needsMoveTo = false;
out.moveTo(x0, y0);
out.moveTo(cx0, cy0);
}
emitSeg(pts, off, type);
}
@ -278,8 +320,8 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
}
needsMoveTo = true;
}
this.x0 = x;
this.y0 = y;
this.cx0 = x;
this.cy0 = y;
}
private void goTo_starting(final double[] pts, final int off, final int type) {
@ -305,10 +347,56 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
@Override
public void lineTo(final double x1, final double y1) {
final double dx = x1 - x0;
final double dy = y1 - y0;
final int outcode0 = this.cOutCode;
double len = dx*dx + dy*dy;
if (clipRect != null) {
final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
// Should clip
final int orCode = (outcode0 | outcode1);
if (orCode != 0) {
final int sideCode = outcode0 & outcode1;
// basic rejection criteria:
if (sideCode == 0) {
// ovelap clip:
if (subdivide) {
// avoid reentrance
subdivide = false;
// subdivide curve => callback with subdivided parts:
boolean ret = curveSplitter.splitLine(cx0, cy0, x1, y1,
orCode, this);
// reentrance is done:
subdivide = true;
if (ret) {
return;
}
}
// already subdivided so render it
} else {
this.cOutCode = outcode1;
skipLineTo(x1, y1);
return;
}
}
this.cOutCode = outcode1;
if (this.outside) {
this.outside = false;
// Adjust current index, phase & dash:
skipLen();
}
}
_lineTo(x1, y1);
}
private void _lineTo(final double x1, final double y1) {
final double dx = x1 - cx0;
final double dy = y1 - cy0;
double len = dx * dx + dy * dy;
if (len == 0.0d) {
return;
}
@ -327,8 +415,7 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
boolean _dashOn = dashOn;
double _phase = phase;
double leftInThisDashSegment;
double d, dashdx, dashdy, p;
double leftInThisDashSegment, d;
while (true) {
d = _dash[_idx];
@ -349,24 +436,15 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
_idx = (_idx + 1) % _dashLen;
_dashOn = !_dashOn;
}
// Save local state:
idx = _idx;
dashOn = _dashOn;
phase = _phase;
return;
break;
}
dashdx = d * cx;
dashdy = d * cy;
if (_phase == 0.0d) {
_curCurvepts[0] = x0 + dashdx;
_curCurvepts[1] = y0 + dashdy;
_curCurvepts[0] = cx0 + d * cx;
_curCurvepts[1] = cy0 + d * cy;
} else {
p = leftInThisDashSegment / d;
_curCurvepts[0] = x0 + p * dashdx;
_curCurvepts[1] = y0 + p * dashdy;
_curCurvepts[0] = cx0 + leftInThisDashSegment * cx;
_curCurvepts[1] = cy0 + leftInThisDashSegment * cy;
}
goTo(_curCurvepts, 0, 4, _dashOn);
@ -377,19 +455,95 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
_dashOn = !_dashOn;
_phase = 0.0d;
}
// Save local state:
idx = _idx;
dashOn = _dashOn;
phase = _phase;
}
// shared instance in DDasher
private final LengthIterator li = new LengthIterator();
private void skipLineTo(final double x1, final double y1) {
final double dx = x1 - cx0;
final double dy = y1 - cy0;
double len = dx * dx + dy * dy;
if (len != 0.0d) {
len = Math.sqrt(len);
}
// Accumulate skipped length:
this.outside = true;
this.totalSkipLen += len;
// Fix initial move:
this.needsMoveTo = true;
this.starting = false;
this.cx0 = x1;
this.cy0 = y1;
}
public void skipLen() {
double len = this.totalSkipLen;
this.totalSkipLen = 0.0d;
final double[] _dash = dash;
final int _dashLen = this.dashLen;
int _idx = idx;
boolean _dashOn = dashOn;
double _phase = phase;
// -2 to ensure having 2 iterations of the post-loop
// to compensate the remaining phase
final long fullcycles = (long)Math.floor(len / cycleLen) - 2L;
if (fullcycles > 0L) {
len -= cycleLen * fullcycles;
final long iterations = fullcycles * _dashLen;
_idx = (int) (iterations + _idx) % _dashLen;
_dashOn = (iterations + (_dashOn ? 1L : 0L) & 1L) == 1L;
}
double leftInThisDashSegment, d;
while (true) {
d = _dash[_idx];
leftInThisDashSegment = d - _phase;
if (len <= leftInThisDashSegment) {
// Advance phase within current dash segment
_phase += len;
// TODO: compare double values using epsilon:
if (len == leftInThisDashSegment) {
_phase = 0.0d;
_idx = (_idx + 1) % _dashLen;
_dashOn = !_dashOn;
}
break;
}
len -= leftInThisDashSegment;
// Advance to next dash segment
_idx = (_idx + 1) % _dashLen;
_dashOn = !_dashOn;
_phase = 0.0d;
}
// Save local state:
idx = _idx;
dashOn = _dashOn;
phase = _phase;
}
// preconditions: curCurvepts must be an array of length at least 2 * type,
// that contains the curve we want to dash in the first type elements
private void somethingTo(final int type) {
if (pointCurve(curCurvepts, type)) {
final double[] _curCurvepts = curCurvepts;
if (pointCurve(_curCurvepts, type)) {
return;
}
final LengthIterator _li = li;
final double[] _curCurvepts = curCurvepts;
final double[] _dash = dash;
final int _dashLen = this.dashLen;
@ -401,17 +555,16 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
// initially the current curve is at curCurvepts[0...type]
int curCurveoff = 0;
double lastSplitT = 0.0d;
double prevT = 0.0d;
double t;
double leftInThisDashSegment = _dash[_idx] - _phase;
while ((t = _li.next(leftInThisDashSegment)) < 1.0d) {
if (t != 0.0d) {
DHelpers.subdivideAt((t - lastSplitT) / (1.0d - lastSplitT),
DHelpers.subdivideAt((t - prevT) / (1.0d - prevT),
_curCurvepts, curCurveoff,
_curCurvepts, 0,
_curCurvepts, type, type);
lastSplitT = t;
_curCurvepts, 0, type);
prevT = t;
goTo(_curCurvepts, 2, type, _dashOn);
curCurveoff = type;
}
@ -439,7 +592,29 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
_li.reset();
}
private static boolean pointCurve(double[] curve, int type) {
private void skipSomethingTo(final int type) {
final double[] _curCurvepts = curCurvepts;
if (pointCurve(_curCurvepts, type)) {
return;
}
final LengthIterator _li = li;
_li.initializeIterationOnCurve(_curCurvepts, type);
// In contrary to somethingTo(),
// just estimate properly the curve length:
final double len = _li.totalLength();
// Accumulate skipped length:
this.outside = true;
this.totalSkipLen += len;
// Fix initial move:
this.needsMoveTo = true;
this.starting = false;
}
private static boolean pointCurve(final double[] curve, final int type) {
for (int i = 2; i < type; i++) {
if (curve[i] != curve[i-2]) {
return false;
@ -462,15 +637,14 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
// tree; however, the trees we are interested in have the property that
// every non leaf node has exactly 2 children
static final class LengthIterator {
private enum Side {LEFT, RIGHT}
// Holds the curves at various levels of the recursion. The root
// (i.e. the original curve) is at recCurveStack[0] (but then it
// gets subdivided, the left half is put at 1, so most of the time
// only the right half of the original curve is at 0)
private final double[][] recCurveStack; // dirty
// sides[i] indicates whether the node at level i+1 in the path from
// sidesRight[i] indicates whether the node at level i+1 in the path from
// the root to the current leaf is a left or right child of its parent.
private final Side[] sides; // dirty
private final boolean[] sidesRight; // dirty
private int curveType;
// lastT and nextT delimit the current leaf.
private double nextT;
@ -491,7 +665,7 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
LengthIterator() {
this.recCurveStack = new double[REC_LIMIT + 1][8];
this.sides = new Side[REC_LIMIT];
this.sidesRight = new boolean[REC_LIMIT];
// if any methods are called without first initializing this object
// on a curve, we want it to fail ASAP.
this.nextT = Double.MAX_VALUE;
@ -513,7 +687,7 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
for (int i = recLimit; i >= 0; i--) {
Arrays.fill(recCurveStack[i], 0.0d);
}
Arrays.fill(sides, Side.LEFT);
Arrays.fill(sidesRight, false);
Arrays.fill(curLeafCtrlPolyLengths, 0.0d);
Arrays.fill(nextRoots, 0.0d);
Arrays.fill(flatLeafCoefCache, 0.0d);
@ -521,7 +695,7 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
}
}
void initializeIterationOnCurve(double[] pts, int type) {
void initializeIterationOnCurve(final double[] pts, final int type) {
// optimize arraycopy (8 values faster than 6 = type):
System.arraycopy(pts, 0, recCurveStack[0], 0, 8);
this.curveType = type;
@ -533,11 +707,11 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
goLeft(); // initializes nextT and lenAtNextT properly
this.lenAtLastSplit = 0.0d;
if (recLevel > 0) {
this.sides[0] = Side.LEFT;
this.sidesRight[0] = false;
this.done = false;
} else {
// the root of the tree is a leaf so we're done.
this.sides[0] = Side.RIGHT;
this.sidesRight[0] = true;
this.done = true;
}
this.lastSegLen = 0.0d;
@ -546,7 +720,7 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
// 0 == false, 1 == true, -1 == invalid cached value.
private int cachedHaveLowAcceleration = -1;
private boolean haveLowAcceleration(double err) {
private boolean haveLowAcceleration(final double err) {
if (cachedHaveLowAcceleration == -1) {
final double len1 = curLeafCtrlPolyLengths[0];
final double len2 = curLeafCtrlPolyLengths[1];
@ -613,7 +787,7 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
if (_flatLeafCoefCache[2] < 0.0d) {
double x = curLeafCtrlPolyLengths[0],
y = x + curLeafCtrlPolyLengths[1];
y = x + curLeafCtrlPolyLengths[1];
if (curveType == 8) {
double z = y + curLeafCtrlPolyLengths[2];
_flatLeafCoefCache[0] = 3.0d * (x - y) + z;
@ -635,7 +809,7 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
// we use cubicRootsInAB here, because we want only roots in 0, 1,
// and our quadratic root finder doesn't filter, so it's just a
// matter of convenience.
int n = DHelpers.cubicRootsInAB(a, b, c, d, nextRoots, 0, 0.0d, 1.0d);
final int n = DHelpers.cubicRootsInAB(a, b, c, d, nextRoots, 0, 0.0d, 1.0d);
if (n == 1 && !Double.isNaN(nextRoots[0])) {
t = nextRoots[0];
}
@ -656,6 +830,16 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
return t;
}
double totalLength() {
while (!done) {
goToNextLeaf();
}
// reset LengthIterator:
reset();
return lenAtNextT;
}
double lastSegLen() {
return lastSegLen;
}
@ -665,11 +849,11 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
private void goToNextLeaf() {
// We must go to the first ancestor node that has an unvisited
// right child.
final boolean[] _sides = sidesRight;
int _recLevel = recLevel;
final Side[] _sides = sides;
_recLevel--;
while(_sides[_recLevel] == Side.RIGHT) {
while(_sides[_recLevel]) {
if (_recLevel == 0) {
recLevel = 0;
done = true;
@ -678,19 +862,17 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
_recLevel--;
}
_sides[_recLevel] = Side.RIGHT;
_sides[_recLevel] = true;
// optimize arraycopy (8 values faster than 6 = type):
System.arraycopy(recCurveStack[_recLevel], 0,
recCurveStack[_recLevel+1], 0, 8);
_recLevel++;
System.arraycopy(recCurveStack[_recLevel++], 0,
recCurveStack[_recLevel], 0, 8);
recLevel = _recLevel;
goLeft();
}
// go to the leftmost node from the current node. Return its length.
private void goLeft() {
double len = onLeaf();
final double len = onLeaf();
if (len >= 0.0d) {
lastT = nextT;
lenAtLastT = lenAtNextT;
@ -700,10 +882,11 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
flatLeafCoefCache[2] = -1.0d;
cachedHaveLowAcceleration = -1;
} else {
DHelpers.subdivide(recCurveStack[recLevel], 0,
recCurveStack[recLevel+1], 0,
recCurveStack[recLevel], 0, curveType);
sides[recLevel] = Side.LEFT;
DHelpers.subdivide(recCurveStack[recLevel],
recCurveStack[recLevel + 1],
recCurveStack[recLevel], curveType);
sidesRight[recLevel] = false;
recLevel++;
goLeft();
}
@ -718,7 +901,7 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
double x0 = curve[0], y0 = curve[1];
for (int i = 2; i < _curveType; i += 2) {
final double x1 = curve[i], y1 = curve[i+1];
final double x1 = curve[i], y1 = curve[i + 1];
final double len = DHelpers.linelen(x0, y0, x1, y1);
polyLen += len;
curLeafCtrlPolyLengths[(i >> 1) - 1] = len;
@ -726,10 +909,9 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
y0 = y1;
}
final double lineLen = DHelpers.linelen(curve[0], curve[1],
curve[_curveType-2],
curve[_curveType-1]);
if ((polyLen - lineLen) < ERR || recLevel == REC_LIMIT) {
final double lineLen = DHelpers.linelen(curve[0], curve[1], x0, y0);
if ((polyLen - lineLen) < CURVE_LEN_ERR || recLevel == REC_LIMIT) {
return (polyLen + lineLen) / 2.0d;
}
return -1.0d;
@ -740,42 +922,191 @@ final class DDasher implements DPathConsumer2D, MarlinConst {
public void curveTo(final double x1, final double y1,
final double x2, final double y2,
final double x3, final double y3)
{
final int outcode0 = this.cOutCode;
if (clipRect != null) {
final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
final int outcode3 = DHelpers.outcode(x3, y3, clipRect);
// Should clip
final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);
if (orCode != 0) {
final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;
// basic rejection criteria:
if (sideCode == 0) {
// ovelap clip:
if (subdivide) {
// avoid reentrance
subdivide = false;
// subdivide curve => callback with subdivided parts:
boolean ret = curveSplitter.splitCurve(cx0, cy0, x1, y1, x2, y2, x3, y3,
orCode, this);
// reentrance is done:
subdivide = true;
if (ret) {
return;
}
}
// already subdivided so render it
} else {
this.cOutCode = outcode3;
skipCurveTo(x1, y1, x2, y2, x3, y3);
return;
}
}
this.cOutCode = outcode3;
if (this.outside) {
this.outside = false;
// Adjust current index, phase & dash:
skipLen();
}
}
_curveTo(x1, y1, x2, y2, x3, y3);
}
private void _curveTo(final double x1, final double y1,
final double x2, final double y2,
final double x3, final double y3)
{
final double[] _curCurvepts = curCurvepts;
_curCurvepts[0] = x0; _curCurvepts[1] = y0;
_curCurvepts[2] = x1; _curCurvepts[3] = y1;
_curCurvepts[4] = x2; _curCurvepts[5] = y2;
_curCurvepts[6] = x3; _curCurvepts[7] = y3;
somethingTo(8);
// monotonize curve:
final CurveBasicMonotonizer monotonizer
= rdrCtx.monotonizer.curve(cx0, cy0, x1, y1, x2, y2, x3, y3);
final int nSplits = monotonizer.nbSplits;
final double[] mid = monotonizer.middle;
for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {
// optimize arraycopy (8 values faster than 6 = type):
System.arraycopy(mid, off, _curCurvepts, 0, 8);
somethingTo(8);
}
}
private void skipCurveTo(final double x1, final double y1,
final double x2, final double y2,
final double x3, final double y3)
{
final double[] _curCurvepts = curCurvepts;
_curCurvepts[0] = cx0; _curCurvepts[1] = cy0;
_curCurvepts[2] = x1; _curCurvepts[3] = y1;
_curCurvepts[4] = x2; _curCurvepts[5] = y2;
_curCurvepts[6] = x3; _curCurvepts[7] = y3;
skipSomethingTo(8);
this.cx0 = x3;
this.cy0 = y3;
}
@Override
public void quadTo(final double x1, final double y1,
final double x2, final double y2)
{
final int outcode0 = this.cOutCode;
if (clipRect != null) {
final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
// Should clip
final int orCode = (outcode0 | outcode1 | outcode2);
if (orCode != 0) {
final int sideCode = outcode0 & outcode1 & outcode2;
// basic rejection criteria:
if (sideCode == 0) {
// ovelap clip:
if (subdivide) {
// avoid reentrance
subdivide = false;
// subdivide curve => call lineTo() with subdivided curves:
boolean ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,
x2, y2, orCode, this);
// reentrance is done:
subdivide = true;
if (ret) {
return;
}
}
// already subdivided so render it
} else {
this.cOutCode = outcode2;
skipQuadTo(x1, y1, x2, y2);
return;
}
}
this.cOutCode = outcode2;
if (this.outside) {
this.outside = false;
// Adjust current index, phase & dash:
skipLen();
}
}
_quadTo(x1, y1, x2, y2);
}
private void _quadTo(final double x1, final double y1,
final double x2, final double y2)
{
final double[] _curCurvepts = curCurvepts;
_curCurvepts[0] = x0; _curCurvepts[1] = y0;
_curCurvepts[2] = x1; _curCurvepts[3] = y1;
_curCurvepts[4] = x2; _curCurvepts[5] = y2;
somethingTo(6);
// monotonize quad:
final CurveBasicMonotonizer monotonizer
= rdrCtx.monotonizer.quad(cx0, cy0, x1, y1, x2, y2);
final int nSplits = monotonizer.nbSplits;
final double[] mid = monotonizer.middle;
for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
// optimize arraycopy (8 values faster than 6 = type):
System.arraycopy(mid, off, _curCurvepts, 0, 8);
somethingTo(6);
}
}
private void skipQuadTo(final double x1, final double y1,
final double x2, final double y2)
{
final double[] _curCurvepts = curCurvepts;
_curCurvepts[0] = cx0; _curCurvepts[1] = cy0;
_curCurvepts[2] = x1; _curCurvepts[3] = y1;
_curCurvepts[4] = x2; _curCurvepts[5] = y2;
skipSomethingTo(6);
this.cx0 = x2;
this.cy0 = y2;
}
@Override
public void closePath() {
lineTo(sx, sy);
if (cx0 != sx0 || cy0 != sy0) {
lineTo(sx0, sy0);
}
if (firstSegidx != 0) {
if (!dashOn || needsMoveTo) {
out.moveTo(sx, sy);
out.moveTo(sx0, sy0);
}
emitFirstSegments();
}
moveTo(sx, sy);
moveTo(sx0, sy0);
}
@Override
public void pathDone() {
if (firstSegidx != 0) {
out.moveTo(sx, sy);
out.moveTo(sx0, sy0);
emitFirstSegments();
}
out.pathDone();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2018, 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
@ -25,7 +25,6 @@
package sun.java2d.marlin;
import static java.lang.Math.PI;
import java.util.Arrays;
import sun.java2d.marlin.stats.Histogram;
import sun.java2d.marlin.stats.StatLong;
@ -41,13 +40,25 @@ final class DHelpers implements MarlinConst {
return (d <= err && d >= -err);
}
static int quadraticRoots(final double a, final double b,
final double c, double[] zeroes, final int off)
static double evalCubic(final double a, final double b,
final double c, final double d,
final double t)
{
return t * (t * (t * a + b) + c) + d;
}
static double evalQuad(final double a, final double b,
final double c, final double t)
{
return t * (t * a + b) + c;
}
static int quadraticRoots(final double a, final double b, final double c,
final double[] zeroes, final int off)
{
int ret = off;
double t;
if (a != 0.0d) {
final double dis = b*b - 4*a*c;
final double dis = b*b - 4.0d * a * c;
if (dis > 0.0d) {
final double sqrtDis = Math.sqrt(dis);
// depending on the sign of b we use a slightly different
@ -62,34 +73,34 @@ final class DHelpers implements MarlinConst {
zeroes[ret++] = (2.0d * c) / (-b + sqrtDis);
}
} else if (dis == 0.0d) {
t = (-b) / (2.0d * a);
zeroes[ret++] = t;
}
} else {
if (b != 0.0d) {
t = (-c) / b;
zeroes[ret++] = t;
zeroes[ret++] = -b / (2.0d * a);
}
} else if (b != 0.0d) {
zeroes[ret++] = -c / b;
}
return ret - off;
}
// find the roots of g(t) = d*t^3 + a*t^2 + b*t + c in [A,B)
static int cubicRootsInAB(double d, double a, double b, double c,
double[] pts, final int off,
static int cubicRootsInAB(final double d, double a, double b, double c,
final double[] pts, final int off,
final double A, final double B)
{
if (d == 0.0d) {
int num = quadraticRoots(a, b, c, pts, off);
final int num = quadraticRoots(a, b, c, pts, off);
return filterOutNotInAB(pts, off, num, A, B) - off;
}
// From Graphics Gems:
// http://tog.acm.org/resources/GraphicsGems/gems/Roots3And4.c
// https://github.com/erich666/GraphicsGems/blob/master/gems/Roots3And4.c
// (also from awt.geom.CubicCurve2D. But here we don't need as
// much accuracy and we don't want to create arrays so we use
// our own customized version).
// normal form: x^3 + ax^2 + bx + c = 0
/*
* TODO: cleanup all that code after reading Roots3And4.c
*/
a /= d;
b /= d;
c /= d;
@ -102,63 +113,45 @@ final class DHelpers implements MarlinConst {
// p = P/3
// q = Q/2
// instead and use those values for simplicity of the code.
double sq_A = a * a;
double p = (1.0d/3.0d) * ((-1.0d/3.0d) * sq_A + b);
double q = (1.0d/2.0d) * ((2.0d/27.0d) * a * sq_A - (1.0d/3.0d) * a * b + c);
final double sub = (1.0d / 3.0d) * a;
final double sq_A = a * a;
final double p = (1.0d / 3.0d) * ((-1.0d / 3.0d) * sq_A + b);
final double q = (1.0d / 2.0d) * ((2.0d / 27.0d) * a * sq_A - sub * b + c);
// use Cardano's formula
double cb_p = p * p * p;
double D = q * q + cb_p;
final double cb_p = p * p * p;
final double D = q * q + cb_p;
int num;
if (D < 0.0d) {
// see: http://en.wikipedia.org/wiki/Cubic_function#Trigonometric_.28and_hyperbolic.29_method
final double phi = (1.0d/3.0d) * Math.acos(-q / Math.sqrt(-cb_p));
final double phi = (1.0d / 3.0d) * Math.acos(-q / Math.sqrt(-cb_p));
final double t = 2.0d * Math.sqrt(-p);
pts[ off+0 ] = ( t * Math.cos(phi));
pts[ off+1 ] = (-t * Math.cos(phi + (PI / 3.0d)));
pts[ off+2 ] = (-t * Math.cos(phi - (PI / 3.0d)));
pts[off ] = ( t * Math.cos(phi) - sub);
pts[off + 1] = (-t * Math.cos(phi + (Math.PI / 3.0d)) - sub);
pts[off + 2] = (-t * Math.cos(phi - (Math.PI / 3.0d)) - sub);
num = 3;
} else {
final double sqrt_D = Math.sqrt(D);
final double u = Math.cbrt(sqrt_D - q);
final double v = - Math.cbrt(sqrt_D + q);
pts[ off ] = (u + v);
pts[off ] = (u + v - sub);
num = 1;
if (within(D, 0.0d, 1e-8d)) {
pts[off+1] = -(pts[off] / 2.0d);
pts[off + 1] = ((-1.0d / 2.0d) * (u + v) - sub);
num = 2;
}
}
final double sub = (1.0d/3.0d) * a;
for (int i = 0; i < num; ++i) {
pts[ off+i ] -= sub;
}
return filterOutNotInAB(pts, off, num, A, B) - off;
}
static double evalCubic(final double a, final double b,
final double c, final double d,
final double t)
{
return t * (t * (t * a + b) + c) + d;
}
static double evalQuad(final double a, final double b,
final double c, final double t)
{
return t * (t * a + b) + c;
}
// returns the index 1 past the last valid element remaining after filtering
static int filterOutNotInAB(double[] nums, final int off, final int len,
static int filterOutNotInAB(final double[] nums, final int off, final int len,
final double a, final double b)
{
int ret = off;
@ -170,35 +163,189 @@ final class DHelpers implements MarlinConst {
return ret;
}
static double linelen(double x1, double y1, double x2, double y2) {
final double dx = x2 - x1;
final double dy = y2 - y1;
return Math.sqrt(dx*dx + dy*dy);
static double fastLineLen(final double x0, final double y0,
final double x1, final double y1)
{
final double dx = x1 - x0;
final double dy = y1 - y0;
// use manhattan norm:
return Math.abs(dx) + Math.abs(dy);
}
static void subdivide(double[] src, int srcoff, double[] left, int leftoff,
double[] right, int rightoff, int type)
static double linelen(final double x0, final double y0,
final double x1, final double y1)
{
final double dx = x1 - x0;
final double dy = y1 - y0;
return Math.sqrt(dx * dx + dy * dy);
}
static double fastQuadLen(final double x0, final double y0,
final double x1, final double y1,
final double x2, final double y2)
{
final double dx1 = x1 - x0;
final double dx2 = x2 - x1;
final double dy1 = y1 - y0;
final double dy2 = y2 - y1;
// use manhattan norm:
return Math.abs(dx1) + Math.abs(dx2)
+ Math.abs(dy1) + Math.abs(dy2);
}
static double quadlen(final double x0, final double y0,
final double x1, final double y1,
final double x2, final double y2)
{
return (linelen(x0, y0, x1, y1)
+ linelen(x1, y1, x2, y2)
+ linelen(x0, y0, x2, y2)) / 2.0d;
}
static double fastCurvelen(final double x0, final double y0,
final double x1, final double y1,
final double x2, final double y2,
final double x3, final double y3)
{
final double dx1 = x1 - x0;
final double dx2 = x2 - x1;
final double dx3 = x3 - x2;
final double dy1 = y1 - y0;
final double dy2 = y2 - y1;
final double dy3 = y3 - y2;
// use manhattan norm:
return Math.abs(dx1) + Math.abs(dx2) + Math.abs(dx3)
+ Math.abs(dy1) + Math.abs(dy2) + Math.abs(dy3);
}
static double curvelen(final double x0, final double y0,
final double x1, final double y1,
final double x2, final double y2,
final double x3, final double y3)
{
return (linelen(x0, y0, x1, y1)
+ linelen(x1, y1, x2, y2)
+ linelen(x2, y2, x3, y3)
+ linelen(x0, y0, x3, y3)) / 2.0d;
}
// finds values of t where the curve in pts should be subdivided in order
// to get good offset curves a distance of w away from the middle curve.
// Stores the points in ts, and returns how many of them there were.
static int findSubdivPoints(final DCurve c, final double[] pts,
final double[] ts, final int type,
final double w2)
{
final double x12 = pts[2] - pts[0];
final double y12 = pts[3] - pts[1];
// if the curve is already parallel to either axis we gain nothing
// from rotating it.
if ((y12 != 0.0d && x12 != 0.0d)) {
// we rotate it so that the first vector in the control polygon is
// parallel to the x-axis. This will ensure that rotated quarter
// circles won't be subdivided.
final double hypot = Math.sqrt(x12 * x12 + y12 * y12);
final double cos = x12 / hypot;
final double sin = y12 / hypot;
final double x1 = cos * pts[0] + sin * pts[1];
final double y1 = cos * pts[1] - sin * pts[0];
final double x2 = cos * pts[2] + sin * pts[3];
final double y2 = cos * pts[3] - sin * pts[2];
final double x3 = cos * pts[4] + sin * pts[5];
final double y3 = cos * pts[5] - sin * pts[4];
switch(type) {
case 8:
final double x4 = cos * pts[6] + sin * pts[7];
final double y4 = cos * pts[7] - sin * pts[6];
c.set(x1, y1, x2, y2, x3, y3, x4, y4);
break;
case 6:
c.set(x1, y1, x2, y2, x3, y3);
break;
default:
}
} else {
c.set(pts, type);
}
int ret = 0;
// we subdivide at values of t such that the remaining rotated
// curves are monotonic in x and y.
ret += c.dxRoots(ts, ret);
ret += c.dyRoots(ts, ret);
// subdivide at inflection points.
if (type == 8) {
// quadratic curves can't have inflection points
ret += c.infPoints(ts, ret);
}
// now we must subdivide at points where one of the offset curves will have
// a cusp. This happens at ts where the radius of curvature is equal to w.
ret += c.rootsOfROCMinusW(ts, ret, w2, 0.0001d);
ret = filterOutNotInAB(ts, 0, ret, 0.0001d, 0.9999d);
isort(ts, ret);
return ret;
}
// finds values of t where the curve in pts should be subdivided in order
// to get intersections with the given clip rectangle.
// Stores the points in ts, and returns how many of them there were.
static int findClipPoints(final DCurve curve, final double[] pts,
final double[] ts, final int type,
final int outCodeOR,
final double[] clipRect)
{
curve.set(pts, type);
// clip rectangle (ymin, ymax, xmin, xmax)
int ret = 0;
if ((outCodeOR & OUTCODE_LEFT) != 0) {
ret += curve.xPoints(ts, ret, clipRect[2]);
}
if ((outCodeOR & OUTCODE_RIGHT) != 0) {
ret += curve.xPoints(ts, ret, clipRect[3]);
}
if ((outCodeOR & OUTCODE_TOP) != 0) {
ret += curve.yPoints(ts, ret, clipRect[0]);
}
if ((outCodeOR & OUTCODE_BOTTOM) != 0) {
ret += curve.yPoints(ts, ret, clipRect[1]);
}
isort(ts, ret);
return ret;
}
static void subdivide(final double[] src,
final double[] left, final double[] right,
final int type)
{
switch(type) {
case 6:
DHelpers.subdivideQuad(src, srcoff, left, leftoff, right, rightoff);
return;
case 8:
DHelpers.subdivideCubic(src, srcoff, left, leftoff, right, rightoff);
subdivideCubic(src, left, right);
return;
case 6:
subdivideQuad(src, left, right);
return;
default:
throw new InternalError("Unsupported curve type");
}
}
static void isort(double[] a, int off, int len) {
for (int i = off + 1, end = off + len; i < end; i++) {
double ai = a[i];
int j = i - 1;
for (; j >= off && a[j] > ai; j--) {
a[j+1] = a[j];
static void isort(final double[] a, final int len) {
for (int i = 1, j; i < len; i++) {
final double ai = a[i];
j = i - 1;
for (; j >= 0 && a[j] > ai; j--) {
a[j + 1] = a[j];
}
a[j+1] = ai;
a[j + 1] = ai;
}
}
@ -221,206 +368,216 @@ final class DHelpers implements MarlinConst {
* equals (<code>leftoff</code> + 6), in order
* to avoid allocating extra storage for this common point.
* @param src the array holding the coordinates for the source curve
* @param srcoff the offset into the array of the beginning of the
* the 6 source coordinates
* @param left the array for storing the coordinates for the first
* half of the subdivided curve
* @param leftoff the offset into the array of the beginning of the
* the 6 left coordinates
* @param right the array for storing the coordinates for the second
* half of the subdivided curve
* @param rightoff the offset into the array of the beginning of the
* the 6 right coordinates
* @since 1.7
*/
static void subdivideCubic(double[] src, int srcoff,
double[] left, int leftoff,
double[] right, int rightoff)
static void subdivideCubic(final double[] src,
final double[] left,
final double[] right)
{
double x1 = src[srcoff + 0];
double y1 = src[srcoff + 1];
double ctrlx1 = src[srcoff + 2];
double ctrly1 = src[srcoff + 3];
double ctrlx2 = src[srcoff + 4];
double ctrly2 = src[srcoff + 5];
double x2 = src[srcoff + 6];
double y2 = src[srcoff + 7];
if (left != null) {
left[leftoff + 0] = x1;
left[leftoff + 1] = y1;
}
if (right != null) {
right[rightoff + 6] = x2;
right[rightoff + 7] = y2;
}
x1 = (x1 + ctrlx1) / 2.0d;
y1 = (y1 + ctrly1) / 2.0d;
x2 = (x2 + ctrlx2) / 2.0d;
y2 = (y2 + ctrly2) / 2.0d;
double centerx = (ctrlx1 + ctrlx2) / 2.0d;
double centery = (ctrly1 + ctrly2) / 2.0d;
ctrlx1 = (x1 + centerx) / 2.0d;
ctrly1 = (y1 + centery) / 2.0d;
ctrlx2 = (x2 + centerx) / 2.0d;
ctrly2 = (y2 + centery) / 2.0d;
centerx = (ctrlx1 + ctrlx2) / 2.0d;
centery = (ctrly1 + ctrly2) / 2.0d;
if (left != null) {
left[leftoff + 2] = x1;
left[leftoff + 3] = y1;
left[leftoff + 4] = ctrlx1;
left[leftoff + 5] = ctrly1;
left[leftoff + 6] = centerx;
left[leftoff + 7] = centery;
}
if (right != null) {
right[rightoff + 0] = centerx;
right[rightoff + 1] = centery;
right[rightoff + 2] = ctrlx2;
right[rightoff + 3] = ctrly2;
right[rightoff + 4] = x2;
right[rightoff + 5] = y2;
}
double x1 = src[0];
double y1 = src[1];
double cx1 = src[2];
double cy1 = src[3];
double cx2 = src[4];
double cy2 = src[5];
double x2 = src[6];
double y2 = src[7];
left[0] = x1;
left[1] = y1;
right[6] = x2;
right[7] = y2;
x1 = (x1 + cx1) / 2.0d;
y1 = (y1 + cy1) / 2.0d;
x2 = (x2 + cx2) / 2.0d;
y2 = (y2 + cy2) / 2.0d;
double cx = (cx1 + cx2) / 2.0d;
double cy = (cy1 + cy2) / 2.0d;
cx1 = (x1 + cx) / 2.0d;
cy1 = (y1 + cy) / 2.0d;
cx2 = (x2 + cx) / 2.0d;
cy2 = (y2 + cy) / 2.0d;
cx = (cx1 + cx2) / 2.0d;
cy = (cy1 + cy2) / 2.0d;
left[2] = x1;
left[3] = y1;
left[4] = cx1;
left[5] = cy1;
left[6] = cx;
left[7] = cy;
right[0] = cx;
right[1] = cy;
right[2] = cx2;
right[3] = cy2;
right[4] = x2;
right[5] = y2;
}
static void subdivideCubicAt(double t, double[] src, int srcoff,
double[] left, int leftoff,
double[] right, int rightoff)
static void subdivideCubicAt(final double t,
final double[] src, final int offS,
final double[] pts, final int offL, final int offR)
{
double x1 = src[srcoff + 0];
double y1 = src[srcoff + 1];
double ctrlx1 = src[srcoff + 2];
double ctrly1 = src[srcoff + 3];
double ctrlx2 = src[srcoff + 4];
double ctrly2 = src[srcoff + 5];
double x2 = src[srcoff + 6];
double y2 = src[srcoff + 7];
if (left != null) {
left[leftoff + 0] = x1;
left[leftoff + 1] = y1;
}
if (right != null) {
right[rightoff + 6] = x2;
right[rightoff + 7] = y2;
}
x1 = x1 + t * (ctrlx1 - x1);
y1 = y1 + t * (ctrly1 - y1);
x2 = ctrlx2 + t * (x2 - ctrlx2);
y2 = ctrly2 + t * (y2 - ctrly2);
double centerx = ctrlx1 + t * (ctrlx2 - ctrlx1);
double centery = ctrly1 + t * (ctrly2 - ctrly1);
ctrlx1 = x1 + t * (centerx - x1);
ctrly1 = y1 + t * (centery - y1);
ctrlx2 = centerx + t * (x2 - centerx);
ctrly2 = centery + t * (y2 - centery);
centerx = ctrlx1 + t * (ctrlx2 - ctrlx1);
centery = ctrly1 + t * (ctrly2 - ctrly1);
if (left != null) {
left[leftoff + 2] = x1;
left[leftoff + 3] = y1;
left[leftoff + 4] = ctrlx1;
left[leftoff + 5] = ctrly1;
left[leftoff + 6] = centerx;
left[leftoff + 7] = centery;
}
if (right != null) {
right[rightoff + 0] = centerx;
right[rightoff + 1] = centery;
right[rightoff + 2] = ctrlx2;
right[rightoff + 3] = ctrly2;
right[rightoff + 4] = x2;
right[rightoff + 5] = y2;
}
double x1 = src[offS ];
double y1 = src[offS + 1];
double cx1 = src[offS + 2];
double cy1 = src[offS + 3];
double cx2 = src[offS + 4];
double cy2 = src[offS + 5];
double x2 = src[offS + 6];
double y2 = src[offS + 7];
pts[offL ] = x1;
pts[offL + 1] = y1;
pts[offR + 6] = x2;
pts[offR + 7] = y2;
x1 = x1 + t * (cx1 - x1);
y1 = y1 + t * (cy1 - y1);
x2 = cx2 + t * (x2 - cx2);
y2 = cy2 + t * (y2 - cy2);
double cx = cx1 + t * (cx2 - cx1);
double cy = cy1 + t * (cy2 - cy1);
cx1 = x1 + t * (cx - x1);
cy1 = y1 + t * (cy - y1);
cx2 = cx + t * (x2 - cx);
cy2 = cy + t * (y2 - cy);
cx = cx1 + t * (cx2 - cx1);
cy = cy1 + t * (cy2 - cy1);
pts[offL + 2] = x1;
pts[offL + 3] = y1;
pts[offL + 4] = cx1;
pts[offL + 5] = cy1;
pts[offL + 6] = cx;
pts[offL + 7] = cy;
pts[offR ] = cx;
pts[offR + 1] = cy;
pts[offR + 2] = cx2;
pts[offR + 3] = cy2;
pts[offR + 4] = x2;
pts[offR + 5] = y2;
}
static void subdivideQuad(double[] src, int srcoff,
double[] left, int leftoff,
double[] right, int rightoff)
static void subdivideQuad(final double[] src,
final double[] left,
final double[] right)
{
double x1 = src[srcoff + 0];
double y1 = src[srcoff + 1];
double ctrlx = src[srcoff + 2];
double ctrly = src[srcoff + 3];
double x2 = src[srcoff + 4];
double y2 = src[srcoff + 5];
if (left != null) {
left[leftoff + 0] = x1;
left[leftoff + 1] = y1;
}
if (right != null) {
right[rightoff + 4] = x2;
right[rightoff + 5] = y2;
}
x1 = (x1 + ctrlx) / 2.0d;
y1 = (y1 + ctrly) / 2.0d;
x2 = (x2 + ctrlx) / 2.0d;
y2 = (y2 + ctrly) / 2.0d;
ctrlx = (x1 + x2) / 2.0d;
ctrly = (y1 + y2) / 2.0d;
if (left != null) {
left[leftoff + 2] = x1;
left[leftoff + 3] = y1;
left[leftoff + 4] = ctrlx;
left[leftoff + 5] = ctrly;
}
if (right != null) {
right[rightoff + 0] = ctrlx;
right[rightoff + 1] = ctrly;
right[rightoff + 2] = x2;
right[rightoff + 3] = y2;
}
double x1 = src[0];
double y1 = src[1];
double cx = src[2];
double cy = src[3];
double x2 = src[4];
double y2 = src[5];
left[0] = x1;
left[1] = y1;
right[4] = x2;
right[5] = y2;
x1 = (x1 + cx) / 2.0d;
y1 = (y1 + cy) / 2.0d;
x2 = (x2 + cx) / 2.0d;
y2 = (y2 + cy) / 2.0d;
cx = (x1 + x2) / 2.0d;
cy = (y1 + y2) / 2.0d;
left[2] = x1;
left[3] = y1;
left[4] = cx;
left[5] = cy;
right[0] = cx;
right[1] = cy;
right[2] = x2;
right[3] = y2;
}
static void subdivideQuadAt(double t, double[] src, int srcoff,
double[] left, int leftoff,
double[] right, int rightoff)
static void subdivideQuadAt(final double t,
final double[] src, final int offS,
final double[] pts, final int offL, final int offR)
{
double x1 = src[srcoff + 0];
double y1 = src[srcoff + 1];
double ctrlx = src[srcoff + 2];
double ctrly = src[srcoff + 3];
double x2 = src[srcoff + 4];
double y2 = src[srcoff + 5];
if (left != null) {
left[leftoff + 0] = x1;
left[leftoff + 1] = y1;
}
if (right != null) {
right[rightoff + 4] = x2;
right[rightoff + 5] = y2;
}
x1 = x1 + t * (ctrlx - x1);
y1 = y1 + t * (ctrly - y1);
x2 = ctrlx + t * (x2 - ctrlx);
y2 = ctrly + t * (y2 - ctrly);
ctrlx = x1 + t * (x2 - x1);
ctrly = y1 + t * (y2 - y1);
if (left != null) {
left[leftoff + 2] = x1;
left[leftoff + 3] = y1;
left[leftoff + 4] = ctrlx;
left[leftoff + 5] = ctrly;
}
if (right != null) {
right[rightoff + 0] = ctrlx;
right[rightoff + 1] = ctrly;
right[rightoff + 2] = x2;
right[rightoff + 3] = y2;
}
double x1 = src[offS ];
double y1 = src[offS + 1];
double cx = src[offS + 2];
double cy = src[offS + 3];
double x2 = src[offS + 4];
double y2 = src[offS + 5];
pts[offL ] = x1;
pts[offL + 1] = y1;
pts[offR + 4] = x2;
pts[offR + 5] = y2;
x1 = x1 + t * (cx - x1);
y1 = y1 + t * (cy - y1);
x2 = cx + t * (x2 - cx);
y2 = cy + t * (y2 - cy);
cx = x1 + t * (x2 - x1);
cy = y1 + t * (y2 - y1);
pts[offL + 2] = x1;
pts[offL + 3] = y1;
pts[offL + 4] = cx;
pts[offL + 5] = cy;
pts[offR ] = cx;
pts[offR + 1] = cy;
pts[offR + 2] = x2;
pts[offR + 3] = y2;
}
static void subdivideAt(double t, double[] src, int srcoff,
double[] left, int leftoff,
double[] right, int rightoff, int size)
static void subdivideLineAt(final double t,
final double[] src, final int offS,
final double[] pts, final int offL, final int offR)
{
switch(size) {
case 8:
subdivideCubicAt(t, src, srcoff, left, leftoff, right, rightoff);
return;
case 6:
subdivideQuadAt(t, src, srcoff, left, leftoff, right, rightoff);
return;
double x1 = src[offS ];
double y1 = src[offS + 1];
double x2 = src[offS + 2];
double y2 = src[offS + 3];
pts[offL ] = x1;
pts[offL + 1] = y1;
pts[offR + 2] = x2;
pts[offR + 3] = y2;
x1 = x1 + t * (x2 - x1);
y1 = y1 + t * (y2 - y1);
pts[offL + 2] = x1;
pts[offL + 3] = y1;
pts[offR ] = x1;
pts[offR + 1] = y1;
}
static void subdivideAt(final double t,
final double[] src, final int offS,
final double[] pts, final int offL, final int type)
{
// if instead of switch (perf + most probable cases first)
if (type == 8) {
subdivideCubicAt(t, src, offS, pts, offL, offL + type);
} else if (type == 4) {
subdivideLineAt(t, src, offS, pts, offL, offL + type);
} else {
subdivideQuadAt(t, src, offS, pts, offL, offL + type);
}
}
@ -608,12 +765,12 @@ final class DHelpers implements MarlinConst {
e += 2;
continue;
case TYPE_QUADTO:
io.quadTo(_curves[e+0], _curves[e+1],
io.quadTo(_curves[e], _curves[e+1],
_curves[e+2], _curves[e+3]);
e += 4;
continue;
case TYPE_CUBICTO:
io.curveTo(_curves[e+0], _curves[e+1],
io.curveTo(_curves[e], _curves[e+1],
_curves[e+2], _curves[e+3],
_curves[e+4], _curves[e+5]);
e += 6;
@ -651,12 +808,12 @@ final class DHelpers implements MarlinConst {
continue;
case TYPE_QUADTO:
e -= 4;
io.quadTo(_curves[e+0], _curves[e+1],
io.quadTo(_curves[e], _curves[e+1],
_curves[e+2], _curves[e+3]);
continue;
case TYPE_CUBICTO:
e -= 6;
io.curveTo(_curves[e+0], _curves[e+1],
io.curveTo(_curves[e], _curves[e+1],
_curves[e+2], _curves[e+3],
_curves[e+4], _curves[e+5]);
continue;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2018, 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
@ -31,6 +31,7 @@ import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.security.AccessController;
import sun.awt.geom.PathConsumer2D;
import static sun.java2d.marlin.MarlinUtils.logInfo;
import sun.java2d.ReentrantContextProvider;
import sun.java2d.ReentrantContextProviderCLQ;
@ -46,7 +47,21 @@ import sun.security.action.GetPropertyAction;
public final class DMarlinRenderingEngine extends RenderingEngine
implements MarlinConst
{
private static enum NormMode {
// slightly slower ~2% if enabled stroker clipping (lines) but skipping cap / join handling is few percents faster in specific cases
static final boolean DISABLE_2ND_STROKER_CLIPPING = true;
static final boolean DO_TRACE_PATH = false;
static final boolean DO_CLIP = MarlinProperties.isDoClip();
static final boolean DO_CLIP_FILL = true;
static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag();
private static final float MIN_PEN_SIZE = 1.0f / MIN_SUBPIXELS;
static final double UPPER_BND = Float.MAX_VALUE / 2.0d;
static final double LOWER_BND = -UPPER_BND;
private enum NormMode {
ON_WITH_AA {
@Override
PathIterator getNormalizingPathIterator(final DRendererContext rdrCtx,
@ -79,18 +94,6 @@ public final class DMarlinRenderingEngine extends RenderingEngine
PathIterator src);
}
private static final float MIN_PEN_SIZE = 1.0f / NORM_SUBPIXELS;
static final double UPPER_BND = Float.MAX_VALUE / 2.0d;
static final double LOWER_BND = -UPPER_BND;
static final boolean DO_CLIP = MarlinProperties.isDoClip();
static final boolean DO_CLIP_FILL = true;
static final boolean DO_TRACE_PATH = false;
static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag();
/**
* Public constructor
*/
@ -186,7 +189,7 @@ public final class DMarlinRenderingEngine extends RenderingEngine
boolean thin,
boolean normalize,
boolean antialias,
final sun.awt.geom.PathConsumer2D consumer)
final PathConsumer2D consumer)
{
final NormMode norm = (normalize) ?
((antialias) ? NormMode.ON_WITH_AA : NormMode.ON_NO_AA)
@ -424,11 +427,24 @@ public final class DMarlinRenderingEngine extends RenderingEngine
pc2d = transformerPC2D.deltaTransformConsumer(pc2d, strokerat);
// stroker will adjust the clip rectangle (width / miter limit):
pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit, scale);
pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit, scale,
(dashesD == null));
// Curve Monotizer:
rdrCtx.monotonizer.init(width);
if (dashesD != null) {
if (DO_TRACE_PATH) {
pc2d = transformerPC2D.traceDasher(pc2d);
}
pc2d = rdrCtx.dasher.init(pc2d, dashesD, dashLen, dashphase,
recycleDashes);
if (DISABLE_2ND_STROKER_CLIPPING) {
// disable stoker clipping:
rdrCtx.stroker.disableClipping();
}
} else if (rdrCtx.doClip && (caps != Stroker.CAP_BUTT)) {
if (DO_TRACE_PATH) {
pc2d = transformerPC2D.traceClosedPathDetector(pc2d);
@ -627,6 +643,12 @@ public final class DMarlinRenderingEngine extends RenderingEngine
private static void pathTo(final DRendererContext rdrCtx, final PathIterator pi,
DPathConsumer2D pc2d)
{
if (USE_PATH_SIMPLIFIER) {
// Use path simplifier at the first step
// to remove useless points
pc2d = rdrCtx.pathSimplifier.init(pc2d);
}
// mark context as DIRTY:
rdrCtx.dirty = true;
@ -851,8 +873,6 @@ public final class DMarlinRenderingEngine extends RenderingEngine
// trace Input:
pc2d = rdrCtx.transformerPC2D.traceInput(pc2d);
}
// TODO: subdivide quad/cubic curves into monotonic curves ?
pathTo(rdrCtx, pi, pc2d);
} else {
@ -1002,14 +1022,17 @@ public final class DMarlinRenderingEngine extends RenderingEngine
final String refType = AccessController.doPrivileged(
new GetPropertyAction("sun.java2d.renderer.useRef",
"soft"));
// Java 1.6 does not support strings in switch:
if ("hard".equalsIgnoreCase(refType)) {
REF_TYPE = ReentrantContextProvider.REF_HARD;
} else if ("weak".equalsIgnoreCase(refType)) {
REF_TYPE = ReentrantContextProvider.REF_WEAK;
} else {
REF_TYPE = ReentrantContextProvider.REF_SOFT;
switch (refType) {
default:
case "soft":
REF_TYPE = ReentrantContextProvider.REF_SOFT;
break;
case "weak":
REF_TYPE = ReentrantContextProvider.REF_WEAK;
break;
case "hard":
REF_TYPE = ReentrantContextProvider.REF_HARD;
break;
}
if (USE_THREAD_LOCAL) {
@ -1069,8 +1092,10 @@ public final class DMarlinRenderingEngine extends RenderingEngine
logInfo("sun.java2d.renderer.edges = "
+ MarlinConst.INITIAL_EDGES_COUNT);
logInfo("sun.java2d.renderer.pixelsize = "
+ MarlinConst.INITIAL_PIXEL_DIM);
logInfo("sun.java2d.renderer.pixelWidth = "
+ MarlinConst.INITIAL_PIXEL_WIDTH);
logInfo("sun.java2d.renderer.pixelHeight = "
+ MarlinConst.INITIAL_PIXEL_HEIGHT);
logInfo("sun.java2d.renderer.subPixel_log2_X = "
+ MarlinConst.SUBPIXEL_LG_POSITIONS_X);
@ -1100,12 +1125,21 @@ public final class DMarlinRenderingEngine extends RenderingEngine
// optimisation parameters
logInfo("sun.java2d.renderer.useSimplifier = "
+ MarlinConst.USE_SIMPLIFIER);
logInfo("sun.java2d.renderer.usePathSimplifier= "
+ MarlinConst.USE_PATH_SIMPLIFIER);
logInfo("sun.java2d.renderer.pathSimplifier.pixTol = "
+ MarlinProperties.getPathSimplifierPixelTolerance());
logInfo("sun.java2d.renderer.clip = "
+ MarlinProperties.isDoClip());
logInfo("sun.java2d.renderer.clip.runtime.enable = "
+ MarlinProperties.isDoClipRuntimeFlag());
logInfo("sun.java2d.renderer.clip.subdivider = "
+ MarlinProperties.isDoClipSubdivider());
logInfo("sun.java2d.renderer.clip.subdivider.minLength = "
+ MarlinProperties.getSubdividerMinLength());
// debugging parameters
logInfo("sun.java2d.renderer.doStats = "
+ MarlinConst.DO_STATS);
@ -1123,6 +1157,8 @@ public final class DMarlinRenderingEngine extends RenderingEngine
+ MarlinConst.LOG_UNSAFE_MALLOC);
// quality settings
logInfo("sun.java2d.renderer.curve_len_err = "
+ MarlinProperties.getCurveLengthError());
logInfo("sun.java2d.renderer.cubic_dec_d2 = "
+ MarlinProperties.getCubicDecD2());
logInfo("sun.java2d.renderer.cubic_inc_d1 = "

View File

@ -0,0 +1,136 @@
/*
* Copyright (c) 2017, 2018, 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.marlin;
final class DPathSimplifier implements DPathConsumer2D {
// distance threshold in pixels (device)
private static final double PIX_THRESHOLD = MarlinProperties.getPathSimplifierPixelTolerance();
private static final double SQUARE_TOLERANCE = PIX_THRESHOLD * PIX_THRESHOLD;
// members:
private DPathConsumer2D delegate;
private double cx, cy;
DPathSimplifier() {
}
DPathSimplifier init(final DPathConsumer2D delegate) {
this.delegate = delegate;
return this; // fluent API
}
@Override
public void pathDone() {
delegate.pathDone();
}
@Override
public void closePath() {
delegate.closePath();
}
@Override
public long getNativeConsumer() {
return 0;
}
@Override
public void quadTo(final double x1, final double y1,
final double xe, final double ye)
{
// Test if curve is too small:
double dx = (xe - cx);
double dy = (ye - cy);
if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) {
// check control points P1:
dx = (x1 - cx);
dy = (y1 - cy);
if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) {
return;
}
}
delegate.quadTo(x1, y1, xe, ye);
// final end point:
cx = xe;
cy = ye;
}
@Override
public void curveTo(final double x1, final double y1,
final double x2, final double y2,
final double xe, final double ye)
{
// Test if curve is too small:
double dx = (xe - cx);
double dy = (ye - cy);
if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) {
// check control points P1:
dx = (x1 - cx);
dy = (y1 - cy);
if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) {
// check control points P2:
dx = (x2 - cx);
dy = (y2 - cy);
if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) {
return;
}
}
}
delegate.curveTo(x1, y1, x2, y2, xe, ye);
// final end point:
cx = xe;
cy = ye;
}
@Override
public void moveTo(final double xe, final double ye) {
delegate.moveTo(xe, ye);
// starting point:
cx = xe;
cy = ye;
}
@Override
public void lineTo(final double xe, final double ye) {
// Test if segment is too small:
double dx = (xe - cx);
double dy = (ye - cy);
if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) {
return;
}
delegate.lineTo(xe, ye);
// final end point:
cx = xe;
cy = ye;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2018, 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
@ -53,9 +53,9 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
private static final int SUBPIXEL_TILE
= TILE_H << SUBPIXEL_LG_POSITIONS_Y;
// 2048 (pixelSize) pixels (height) x 8 subpixels = 64K
// 2176 pixels (height) x 8 subpixels = 68K
static final int INITIAL_BUCKET_ARRAY
= INITIAL_PIXEL_DIM * SUBPIXEL_POSITIONS_Y;
= INITIAL_PIXEL_HEIGHT * SUBPIXEL_POSITIONS_Y;
// crossing capacity = edges count / 4 ~ 1024
static final int INITIAL_CROSSING_COUNT = INITIAL_EDGES_COUNT >> 2;
@ -76,13 +76,17 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
// curve break into lines
// cubic error in subpixels to decrement step
private static final double CUB_DEC_ERR_SUBPIX
= MarlinProperties.getCubicDecD2() * (NORM_SUBPIXELS / 8.0d); // 1 pixel
= MarlinProperties.getCubicDecD2() * (SUBPIXEL_POSITIONS_X / 8.0d); // 1.0 / 8th pixel
// cubic error in subpixels to increment step
private static final double CUB_INC_ERR_SUBPIX
= MarlinProperties.getCubicIncD1() * (NORM_SUBPIXELS / 8.0d); // 0.4 pixel
= MarlinProperties.getCubicIncD1() * (SUBPIXEL_POSITIONS_X / 8.0d); // 0.4 / 8th pixel
// scale factor for Y-axis contribution to quad / cubic errors:
public static final double SCALE_DY = ((double) SUBPIXEL_POSITIONS_X) / SUBPIXEL_POSITIONS_Y;
// TestNonAARasterization (JDK-8170879): cubics
// bad paths (59294/100000 == 59,29%, 94335 bad pixels (avg = 1,59), 3966 warnings (avg = 0,07)
// 2018
// 1.0 / 0.2: bad paths (67194/100000 == 67,19%, 117394 bad pixels (avg = 1,75 - max = 9), 4042 warnings (avg = 0,06)
// cubic bind length to decrement step
public static final double CUB_DEC_BND
@ -109,10 +113,12 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
// quad break into lines
// quadratic error in subpixels
private static final double QUAD_DEC_ERR_SUBPIX
= MarlinProperties.getQuadDecD2() * (NORM_SUBPIXELS / 8.0d); // 0.5 pixel
= MarlinProperties.getQuadDecD2() * (SUBPIXEL_POSITIONS_X / 8.0d); // 0.5 / 8th pixel
// TestNonAARasterization (JDK-8170879): quads
// bad paths (62916/100000 == 62,92%, 103818 bad pixels (avg = 1,65), 6514 warnings (avg = 0,10)
// 2018
// 0.50px = bad paths (62915/100000 == 62,92%, 103810 bad pixels (avg = 1,65), 6512 warnings (avg = 0,10)
// quadratic bind length to decrement step
public static final double QUAD_DEC_BND
@ -179,7 +185,7 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
int count = 1; // dt = 1 / count
// maximum(ddX|Y) = norm(dbx, dby) * dt^2 (= 1)
double maxDD = Math.abs(c.dbx) + Math.abs(c.dby);
double maxDD = Math.abs(c.dbx) + Math.abs(c.dby) * SCALE_DY;
final double _DEC_BND = QUAD_DEC_BND;
@ -193,7 +199,8 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
}
}
int nL = 0; // line count
final int nL = count; // line count
if (count > 1) {
final double icount = 1.0d / count; // dt
final double icount2 = icount * icount; // dt^2
@ -203,17 +210,12 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
double dx = c.bx * icount2 + c.cx * icount;
double dy = c.by * icount2 + c.cy * icount;
double x1, y1;
while (--count > 0) {
x1 = x0 + dx;
dx += ddx;
y1 = y0 + dy;
dy += ddy;
// we use x0, y0 to walk the line
for (double x1 = x0, y1 = y0; --count > 0; dx += ddx, dy += ddy) {
x1 += dx;
y1 += dy;
addLine(x0, y0, x1, y1);
if (DO_STATS) { nL++; }
x0 = x1;
y0 = y1;
}
@ -221,7 +223,7 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
addLine(x0, y0, x2, y2);
if (DO_STATS) {
rdrCtx.stats.stat_rdr_quadBreak.add(nL + 1);
rdrCtx.stats.stat_rdr_quadBreak.add(nL);
}
}
@ -234,7 +236,7 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
final DCurve c,
final double x3, final double y3)
{
int count = CUB_COUNT;
int count = CUB_COUNT;
final double icount = CUB_INV_COUNT; // dt
final double icount2 = CUB_INV_COUNT_2; // dt^2
final double icount3 = CUB_INV_COUNT_3; // dt^3
@ -249,34 +251,20 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
dx = c.ax * icount3 + c.bx * icount2 + c.cx * icount;
dy = c.ay * icount3 + c.by * icount2 + c.cy * icount;
// we use x0, y0 to walk the line
double x1 = x0, y1 = y0;
int nL = 0; // line count
final double _DEC_BND = CUB_DEC_BND;
final double _INC_BND = CUB_INC_BND;
final double _SCALE_DY = SCALE_DY;
while (count > 0) {
// divide step by half:
while (Math.abs(ddx) + Math.abs(ddy) >= _DEC_BND) {
dddx /= 8.0d;
dddy /= 8.0d;
ddx = ddx / 4.0d - dddx;
ddy = ddy / 4.0d - dddy;
dx = (dx - ddx) / 2.0d;
dy = (dy - ddy) / 2.0d;
count <<= 1;
if (DO_STATS) {
rdrCtx.stats.stat_rdr_curveBreak_dec.add(count);
}
}
// we use x0, y0 to walk the line
for (double x1 = x0, y1 = y0; count > 0; ) {
// inc / dec => ratio ~ 5 to minimize upscale / downscale but minimize edges
// double step:
// can only do this on even "count" values, because we must divide count by 2
while (count % 2 == 0
&& Math.abs(dx) + Math.abs(dy) <= _INC_BND)
{
while ((count % 2 == 0)
&& ((Math.abs(ddx) + Math.abs(ddy) * _SCALE_DY) <= _INC_BND)) {
dx = 2.0d * dx + ddx;
dy = 2.0d * dy + ddy;
ddx = 4.0d * (ddx + dddx);
@ -289,26 +277,40 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
rdrCtx.stats.stat_rdr_curveBreak_inc.add(count);
}
}
if (--count > 0) {
x1 += dx;
dx += ddx;
ddx += dddx;
y1 += dy;
dy += ddy;
ddy += dddy;
} else {
x1 = x3;
y1 = y3;
// divide step by half:
while ((Math.abs(ddx) + Math.abs(ddy) * _SCALE_DY) >= _DEC_BND) {
dddx /= 8.0d;
dddy /= 8.0d;
ddx = ddx / 4.0d - dddx;
ddy = ddy / 4.0d - dddy;
dx = (dx - ddx) / 2.0d;
dy = (dy - ddy) / 2.0d;
count <<= 1;
if (DO_STATS) {
rdrCtx.stats.stat_rdr_curveBreak_dec.add(count);
}
}
if (--count == 0) {
break;
}
addLine(x0, y0, x1, y1);
x1 += dx;
y1 += dy;
dx += ddx;
dy += ddy;
ddx += dddx;
ddy += dddy;
if (DO_STATS) { nL++; }
addLine(x0, y0, x1, y1);
x0 = x1;
y0 = y1;
}
addLine(x0, y0, x3, y3);
if (DO_STATS) {
rdrCtx.stats.stat_rdr_curveBreak.add(nL);
rdrCtx.stats.stat_rdr_curveBreak.add(nL + 1);
}
}
@ -533,8 +535,8 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
edgeBuckets = edgeBuckets_ref.initial;
edgeBucketCounts = edgeBucketCounts_ref.initial;
// 2048 (pixelsize) pixel large
alphaLine_ref = rdrCtx.newCleanIntArrayRef(INITIAL_AA_ARRAY); // 8K
// 4096 pixels large
alphaLine_ref = rdrCtx.newCleanIntArrayRef(INITIAL_AA_ARRAY); // 16K
alphaLine = alphaLine_ref.initial;
crossings_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K
@ -692,8 +694,10 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
{
final double xe = tosubpixx(pix_x3);
final double ye = tosubpixy(pix_y3);
curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1),
tosubpixx(pix_x2), tosubpixy(pix_y2), xe, ye);
curve.set(x0, y0,
tosubpixx(pix_x1), tosubpixy(pix_y1),
tosubpixx(pix_x2), tosubpixy(pix_y2),
xe, ye);
curveBreakIntoLinesAndAdd(x0, y0, curve, xe, ye);
x0 = xe;
y0 = ye;
@ -705,7 +709,9 @@ final class DRenderer implements DPathConsumer2D, MarlinRenderer {
{
final double xe = tosubpixx(pix_x2);
final double ye = tosubpixy(pix_y2);
curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1), xe, ye);
curve.set(x0, y0,
tosubpixx(pix_x1), tosubpixy(pix_y1),
xe, ye);
quadBreakIntoLinesAndAdd(x0, y0, curve, xe, ye);
x0 = xe;
y0 = ye;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2018, 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
@ -31,6 +31,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import sun.java2d.ReentrantContext;
import sun.java2d.marlin.ArrayCacheConst.CacheStats;
import sun.java2d.marlin.DMarlinRenderingEngine.NormalizingPathIterator;
import sun.java2d.marlin.DTransformingPathConsumer2D.CurveBasicMonotonizer;
import sun.java2d.marlin.DTransformingPathConsumer2D.CurveClipSplitter;
/**
* This class is a renderer context dedicated to a single thread
@ -70,6 +72,8 @@ final class DRendererContext extends ReentrantContext implements IRendererContex
final DStroker stroker;
// Simplifies out collinear lines
final DCollinearSimplifier simplifier = new DCollinearSimplifier();
// Simplifies path
final DPathSimplifier pathSimplifier = new DPathSimplifier();
final DDasher dasher;
final MarlinTileGenerator ptg;
final MarlinCache cache;
@ -81,6 +85,10 @@ final class DRendererContext extends ReentrantContext implements IRendererContex
boolean closedPath = false;
// clip rectangle (ymin, ymax, xmin, xmax):
final double[] clipRect = new double[4];
// CurveBasicMonotonizer instance
final CurveBasicMonotonizer monotonizer;
// CurveClipSplitter instance
final CurveClipSplitter curveClipSplitter;
// Array caches:
/* clean int[] cache (zero-filled) = 5 refs */
@ -124,6 +132,10 @@ final class DRendererContext extends ReentrantContext implements IRendererContex
nPCPathIterator = new NormalizingPathIterator.NearestPixelCenter(double6);
nPQPathIterator = new NormalizingPathIterator.NearestPixelQuarter(double6);
// curve monotonizer & clip subdivider (before transformerPC2D init)
monotonizer = new CurveBasicMonotonizer(this);
curveClipSplitter = new CurveClipSplitter(this);
// MarlinRenderingEngine.TransformingPathConsumer2D
transformerPC2D = new DTransformingPathConsumer2D(this);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2018, 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
@ -27,6 +27,8 @@ package sun.java2d.marlin;
import java.util.Arrays;
import sun.java2d.marlin.DHelpers.PolyStack;
import sun.java2d.marlin.DTransformingPathConsumer2D.CurveBasicMonotonizer;
import sun.java2d.marlin.DTransformingPathConsumer2D.CurveClipSplitter;
// TODO: some of the arithmetic here is too verbose and prone to hard to
// debug typos. We should consider making a small Point/Vector class that
@ -37,10 +39,9 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
private static final int CLOSE = 2;
// pisces used to use fixed point arithmetic with 16 decimal digits. I
// didn't want to change the values of the constant below when I converted
// it to floating point, so that's why the divisions by 2^16 are there.
private static final double ROUND_JOIN_THRESHOLD = 1000.0d/65536.0d;
// round join threshold = 1 subpixel
private static final double ERR_JOIN = (1.0f / MIN_SUBPIXELS);
private static final double ROUND_JOIN_THRESHOLD = ERR_JOIN * ERR_JOIN;
// kappa = (4/3) * (SQRT(2) - 1)
private static final double C = (4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d);
@ -48,8 +49,6 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
// SQRT(2)
private static final double SQRT_2 = Math.sqrt(2.0d);
private static final int MAX_N_CURVES = 11;
private DPathConsumer2D out;
private int capStyle;
@ -80,12 +79,8 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
private final PolyStack reverse;
// This is where the curve to be processed is put. We give it
// enough room to store all curves.
private final double[] middle = new double[MAX_N_CURVES * 6 + 2];
private final double[] lp = new double[8];
private final double[] rp = new double[8];
private final double[] subdivTs = new double[MAX_N_CURVES - 1];
// per-thread renderer context
final DRendererContext rdrCtx;
@ -106,6 +101,11 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
private boolean opened = false;
// flag indicating if the starting point's cap is done
private boolean capStart = false;
// flag indicating to monotonize curves
private boolean monotonize;
private boolean subdivide = false;
private final CurveClipSplitter curveSplitter;
/**
* Constructs a <code>DStroker</code>.
@ -124,6 +124,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
: new PolyStack(rdrCtx);
this.curve = rdrCtx.curve;
this.curveSplitter = rdrCtx.curveClipSplitter;
}
/**
@ -139,6 +140,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
* <code>JOIN_BEVEL</code>.
* @param miterLimit the desired miter limit
* @param scale scaling factor applied to clip boundaries
* @param subdivideCurves true to indicate to subdivide curves, false if dasher does
* @return this instance
*/
DStroker init(final DPathConsumer2D pc2d,
@ -146,12 +148,15 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
final int capStyle,
final int joinStyle,
final double miterLimit,
final double scale)
final double scale,
final boolean subdivideCurves)
{
this.out = pc2d;
this.lineWidth2 = lineWidth / 2.0d;
this.invHalfLineWidth2Sq = 1.0d / (2.0d * lineWidth2 * lineWidth2);
this.monotonize = subdivideCurves;
this.capStyle = capStyle;
this.joinStyle = joinStyle;
@ -189,6 +194,15 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
_clipRect[2] -= margin - rdrOffX;
_clipRect[3] += margin + rdrOffX;
this.clipRect = _clipRect;
// initialize curve splitter here for stroker & dasher:
if (DO_CLIP_SUBDIVIDER) {
subdivide = subdivideCurves;
// adjust padded clip rectangle:
curveSplitter.init();
} else {
subdivide = false;
}
} else {
this.clipRect = null;
this.cOutCode = 0;
@ -197,6 +211,12 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
return this; // fluent API
}
void disableClipping() {
this.clipRect = null;
this.cOutCode = 0;
this.sOutCode = 0;
}
/**
* Disposes this stroker:
* clean up before reusing this instance
@ -213,10 +233,8 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
Arrays.fill(offset1, 0.0d);
Arrays.fill(offset2, 0.0d);
Arrays.fill(miter, 0.0d);
Arrays.fill(middle, 0.0d);
Arrays.fill(lp, 0.0d);
Arrays.fill(rp, 0.0d);
Arrays.fill(subdivTs, 0.0d);
}
}
@ -248,19 +266,20 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
return dx1 * dy2 <= dy1 * dx2;
}
private void drawRoundJoin(double x, double y,
double omx, double omy, double mx, double my,
boolean rev,
double threshold)
private void mayDrawRoundJoin(double cx, double cy,
double omx, double omy,
double mx, double my,
boolean rev)
{
if ((omx == 0.0d && omy == 0.0d) || (mx == 0.0d && my == 0.0d)) {
return;
}
double domx = omx - mx;
double domy = omy - my;
double len = domx*domx + domy*domy;
if (len < threshold) {
final double domx = omx - mx;
final double domy = omy - my;
final double lenSq = domx*domx + domy*domy;
if (lenSq < ROUND_JOIN_THRESHOLD) {
return;
}
@ -270,7 +289,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
mx = -mx;
my = -my;
}
drawRoundJoin(x, y, omx, omy, mx, my, rev);
drawRoundJoin(cx, cy, omx, omy, mx, my, rev);
}
private void drawRoundJoin(double cx, double cy,
@ -381,7 +400,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
final double x1, final double y1,
final double x0p, final double y0p,
final double x1p, final double y1p,
final double[] m, int off)
final double[] m)
{
double x10 = x1 - x0;
double y10 = y1 - y0;
@ -400,8 +419,8 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
double den = x10*y10p - x10p*y10;
double t = x10p*(y0-y0p) - y10p*(x0-x0p);
t /= den;
m[off++] = x0 + t*x10;
m[off] = y0 + t*y10;
m[0] = x0 + t*x10;
m[1] = y0 + t*y10;
}
// Return the intersection point of the lines (x0, y0) -> (x1, y1)
@ -410,7 +429,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
final double x1, final double y1,
final double x0p, final double y0p,
final double x1p, final double y1p,
final double[] m, int off)
final double[] m)
{
double x10 = x1 - x0;
double y10 = y1 - y0;
@ -428,20 +447,21 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
// immediately).
double den = x10*y10p - x10p*y10;
if (den == 0.0d) {
m[off++] = (x0 + x0p) / 2.0d;
m[off] = (y0 + y0p) / 2.0d;
return;
m[2] = (x0 + x0p) / 2.0d;
m[3] = (y0 + y0p) / 2.0d;
} else {
double t = x10p*(y0-y0p) - y10p*(x0-x0p);
t /= den;
m[2] = x0 + t*x10;
m[3] = y0 + t*y10;
}
double t = x10p*(y0-y0p) - y10p*(x0-x0p);
t /= den;
m[off++] = x0 + t*x10;
m[off] = y0 + t*y10;
}
private void drawMiter(final double pdx, final double pdy,
final double x0, final double y0,
final double dx, final double dy,
double omx, double omy, double mx, double my,
double omx, double omy,
double mx, double my,
boolean rev)
{
if ((mx == omx && my == omy) ||
@ -459,8 +479,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
}
computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,
(dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my,
miter, 0);
(dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my, miter);
final double miterX = miter[0];
final double miterY = miter[1];
@ -478,7 +497,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
@Override
public void moveTo(final double x0, final double y0) {
moveTo(x0, y0, cOutCode);
_moveTo(x0, y0, cOutCode);
// update starting point:
this.sx0 = x0;
this.sy0 = y0;
@ -494,7 +513,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
}
}
private void moveTo(final double x0, final double y0,
private void _moveTo(final double x0, final double y0,
final int outcode)
{
if (prev == MOVE_TO) {
@ -521,16 +540,40 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
final boolean force)
{
final int outcode0 = this.cOutCode;
if (!force && clipRect != null) {
final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
this.cOutCode = outcode1;
// basic rejection criteria
if ((outcode0 & outcode1) != 0) {
moveTo(x1, y1, outcode0);
opened = true;
return;
// Should clip
final int orCode = (outcode0 | outcode1);
if (orCode != 0) {
final int sideCode = outcode0 & outcode1;
// basic rejection criteria:
if (sideCode == 0) {
// ovelap clip:
if (subdivide) {
// avoid reentrance
subdivide = false;
// subdivide curve => callback with subdivided parts:
boolean ret = curveSplitter.splitLine(cx0, cy0, x1, y1,
orCode, this);
// reentrance is done:
subdivide = true;
if (ret) {
return;
}
}
// already subdivided so render it
} else {
this.cOutCode = outcode1;
_moveTo(x1, y1, outcode0);
opened = true;
return;
}
}
this.cOutCode = outcode1;
}
double dx = x1 - cx0;
@ -752,10 +795,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
if (joinStyle == JOIN_MITER) {
drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
} else if (joinStyle == JOIN_ROUND) {
drawRoundJoin(x0, y0,
omx, omy,
mx, my, cw,
ROUND_JOIN_THRESHOLD);
mayDrawRoundJoin(x0, y0, omx, omy, mx, my, cw);
}
}
emitLineTo(x0, y0, !cw);
@ -765,18 +805,19 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
private static boolean within(final double x1, final double y1,
final double x2, final double y2,
final double ERR)
final double err)
{
assert ERR > 0 : "";
assert err > 0 : "";
// compare taxicab distance. ERR will always be small, so using
// true distance won't give much benefit
return (DHelpers.within(x1, x2, ERR) && // we want to avoid calling Math.abs
DHelpers.within(y1, y2, ERR)); // this is just as good.
return (DHelpers.within(x1, x2, err) && // we want to avoid calling Math.abs
DHelpers.within(y1, y2, err)); // this is just as good.
}
private void getLineOffsets(double x1, double y1,
double x2, double y2,
double[] left, double[] right) {
private void getLineOffsets(final double x1, final double y1,
final double x2, final double y2,
final double[] left, final double[] right)
{
computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0);
final double mx = offset0[0];
final double my = offset0[1];
@ -784,14 +825,16 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
left[1] = y1 + my;
left[2] = x2 + mx;
left[3] = y2 + my;
right[0] = x1 - mx;
right[1] = y1 - my;
right[2] = x2 - mx;
right[3] = y2 - my;
}
private int computeOffsetCubic(double[] pts, final int off,
double[] leftOff, double[] rightOff)
private int computeOffsetCubic(final double[] pts, final int off,
final double[] leftOff,
final double[] rightOff)
{
// if p1=p2 or p3=p4 it means that the derivative at the endpoint
// vanishes, which creates problems with computeOffset. Usually
@ -800,7 +843,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
// the input curve at the cusp, and passes it to this function.
// because of inaccuracies in the splitting, we consider points
// equal if they're very close to each other.
final double x1 = pts[off + 0], y1 = pts[off + 1];
final double x1 = pts[off ], y1 = pts[off + 1];
final double x2 = pts[off + 2], y2 = pts[off + 3];
final double x3 = pts[off + 4], y3 = pts[off + 5];
final double x4 = pts[off + 6], y4 = pts[off + 7];
@ -814,6 +857,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
// in which case ignore if p1 == p2
final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0d * Math.ulp(y2));
final boolean p3eqp4 = within(x3, y3, x4, y4, 6.0d * Math.ulp(y4));
if (p1eqp2 && p3eqp4) {
getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
return 4;
@ -829,6 +873,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
double dotsq = (dx1 * dx4 + dy1 * dy4);
dotsq *= dotsq;
double l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4;
if (DHelpers.within(dotsq, l1sq * l4sq, 4.0d * Math.ulp(dotsq))) {
getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
return 4;
@ -942,10 +987,11 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
// compute offset curves using bezier spline through t=0.5 (i.e.
// ComputedCurve(0.5) == IdealParallelCurve(0.5))
// return the kind of curve in the right and left arrays.
private int computeOffsetQuad(double[] pts, final int off,
double[] leftOff, double[] rightOff)
private int computeOffsetQuad(final double[] pts, final int off,
final double[] leftOff,
final double[] rightOff)
{
final double x1 = pts[off + 0], y1 = pts[off + 1];
final double x1 = pts[off ], y1 = pts[off + 1];
final double x2 = pts[off + 2], y2 = pts[off + 3];
final double x3 = pts[off + 4], y3 = pts[off + 5];
@ -966,6 +1012,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
// in which case ignore.
final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0d * Math.ulp(y2));
final boolean p2eqp3 = within(x2, y2, x3, y3, 6.0d * Math.ulp(y3));
if (p1eqp2 || p2eqp3) {
getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
return 4;
@ -975,6 +1022,7 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
double dotsq = (dx1 * dx3 + dy1 * dy3);
dotsq *= dotsq;
double l1sq = dx1 * dx1 + dy1 * dy1, l3sq = dx3 * dx3 + dy3 * dy3;
if (DHelpers.within(dotsq, l1sq * l3sq, 4.0d * Math.ulp(dotsq))) {
getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
return 4;
@ -990,151 +1038,111 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
double y1p = y1 + offset0[1]; // point
double x3p = x3 + offset1[0]; // end
double y3p = y3 + offset1[1]; // point
safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff, 2);
safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff);
leftOff[0] = x1p; leftOff[1] = y1p;
leftOff[4] = x3p; leftOff[5] = y3p;
x1p = x1 - offset0[0]; y1p = y1 - offset0[1];
x3p = x3 - offset1[0]; y3p = y3 - offset1[1];
safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff, 2);
safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff);
rightOff[0] = x1p; rightOff[1] = y1p;
rightOff[4] = x3p; rightOff[5] = y3p;
return 6;
}
// finds values of t where the curve in pts should be subdivided in order
// to get good offset curves a distance of w away from the middle curve.
// Stores the points in ts, and returns how many of them there were.
private static int findSubdivPoints(final DCurve c, double[] pts, double[] ts,
final int type, final double w)
{
final double x12 = pts[2] - pts[0];
final double y12 = pts[3] - pts[1];
// if the curve is already parallel to either axis we gain nothing
// from rotating it.
if (y12 != 0.0d && x12 != 0.0d) {
// we rotate it so that the first vector in the control polygon is
// parallel to the x-axis. This will ensure that rotated quarter
// circles won't be subdivided.
final double hypot = Math.sqrt(x12 * x12 + y12 * y12);
final double cos = x12 / hypot;
final double sin = y12 / hypot;
final double x1 = cos * pts[0] + sin * pts[1];
final double y1 = cos * pts[1] - sin * pts[0];
final double x2 = cos * pts[2] + sin * pts[3];
final double y2 = cos * pts[3] - sin * pts[2];
final double x3 = cos * pts[4] + sin * pts[5];
final double y3 = cos * pts[5] - sin * pts[4];
switch(type) {
case 8:
final double x4 = cos * pts[6] + sin * pts[7];
final double y4 = cos * pts[7] - sin * pts[6];
c.set(x1, y1, x2, y2, x3, y3, x4, y4);
break;
case 6:
c.set(x1, y1, x2, y2, x3, y3);
break;
default:
}
} else {
c.set(pts, type);
}
int ret = 0;
// we subdivide at values of t such that the remaining rotated
// curves are monotonic in x and y.
ret += c.dxRoots(ts, ret);
ret += c.dyRoots(ts, ret);
// subdivide at inflection points.
if (type == 8) {
// quadratic curves can't have inflection points
ret += c.infPoints(ts, ret);
}
// now we must subdivide at points where one of the offset curves will have
// a cusp. This happens at ts where the radius of curvature is equal to w.
ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001d);
ret = DHelpers.filterOutNotInAB(ts, 0, ret, 0.0001d, 0.9999d);
DHelpers.isort(ts, 0, ret);
return ret;
}
@Override
public void curveTo(final double x1, final double y1,
final double x2, final double y2,
final double x3, final double y3)
{
final int outcode0 = this.cOutCode;
if (clipRect != null) {
final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
final int outcode3 = DHelpers.outcode(x3, y3, clipRect);
this.cOutCode = outcode3;
if ((outcode0 & outcode3) != 0) {
final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
// Should clip
final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);
if (orCode != 0) {
final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;
// basic rejection criteria
if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) {
moveTo(x3, y3, outcode0);
// basic rejection criteria:
if (sideCode == 0) {
// ovelap clip:
if (subdivide) {
// avoid reentrance
subdivide = false;
// subdivide curve => callback with subdivided parts:
boolean ret = curveSplitter.splitCurve(cx0, cy0, x1, y1,
x2, y2, x3, y3,
orCode, this);
// reentrance is done:
subdivide = true;
if (ret) {
return;
}
}
// already subdivided so render it
} else {
this.cOutCode = outcode3;
_moveTo(x3, y3, outcode0);
opened = true;
return;
}
}
this.cOutCode = outcode3;
}
_curveTo(x1, y1, x2, y2, x3, y3, outcode0);
}
final double[] mid = middle;
mid[0] = cx0; mid[1] = cy0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
mid[6] = x3; mid[7] = y3;
private void _curveTo(final double x1, final double y1,
final double x2, final double y2,
final double x3, final double y3,
final int outcode0)
{
// need these so we can update the state at the end of this method
final double xf = x3, yf = y3;
double dxs = mid[2] - mid[0];
double dys = mid[3] - mid[1];
double dxf = mid[6] - mid[4];
double dyf = mid[7] - mid[5];
double dxs = x1 - cx0;
double dys = y1 - cy0;
double dxf = x3 - x2;
double dyf = y3 - y2;
boolean p1eqp2 = (dxs == 0.0d && dys == 0.0d);
boolean p3eqp4 = (dxf == 0.0d && dyf == 0.0d);
if (p1eqp2) {
dxs = mid[4] - mid[0];
dys = mid[5] - mid[1];
if (dxs == 0.0d && dys == 0.0d) {
dxs = mid[6] - mid[0];
dys = mid[7] - mid[1];
if ((dxs == 0.0d) && (dys == 0.0d)) {
dxs = x2 - cx0;
dys = y2 - cy0;
if ((dxs == 0.0d) && (dys == 0.0d)) {
dxs = x3 - cx0;
dys = y3 - cy0;
}
}
if (p3eqp4) {
dxf = mid[6] - mid[2];
dyf = mid[7] - mid[3];
if (dxf == 0.0d && dyf == 0.0d) {
dxf = mid[6] - mid[0];
dyf = mid[7] - mid[1];
if ((dxf == 0.0d) && (dyf == 0.0d)) {
dxf = x3 - x1;
dyf = y3 - y1;
if ((dxf == 0.0d) && (dyf == 0.0d)) {
dxf = x3 - cx0;
dyf = y3 - cy0;
}
}
if (dxs == 0.0d && dys == 0.0d) {
if ((dxs == 0.0d) && (dys == 0.0d)) {
// this happens if the "curve" is just a point
// fix outcode0 for lineTo() call:
if (clipRect != null) {
this.cOutCode = outcode0;
}
lineTo(mid[0], mid[1]);
lineTo(cx0, cy0);
return;
}
// if these vectors are too small, normalize them, to avoid future
// precision problems.
if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
double len = Math.sqrt(dxs*dxs + dys*dys);
final double len = Math.sqrt(dxs * dxs + dys * dys);
dxs /= len;
dys /= len;
}
if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
double len = Math.sqrt(dxf*dxf + dyf*dyf);
final double len = Math.sqrt(dxf * dxf + dyf * dyf);
dxf /= len;
dyf /= len;
}
@ -1142,17 +1150,25 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
computeOffset(dxs, dys, lineWidth2, offset0);
drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);
double prevT = 0.0d;
for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
final double t = subdivTs[i];
DHelpers.subdivideCubicAt((t - prevT) / (1.0d - prevT),
mid, off, mid, off, mid, off + 6);
prevT = t;
}
int nSplits = 0;
final double[] mid;
final double[] l = lp;
if (monotonize) {
// monotonize curve:
final CurveBasicMonotonizer monotonizer
= rdrCtx.monotonizer.curve(cx0, cy0, x1, y1, x2, y2, x3, y3);
nSplits = monotonizer.nbSplits;
mid = monotonizer.middle;
} else {
// use left instead:
mid = l;
mid[0] = cx0; mid[1] = cy0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
mid[6] = x3; mid[7] = y3;
}
final double[] r = rp;
int kind = 0;
@ -1176,8 +1192,8 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
}
this.prev = DRAWING_OP_TO;
this.cx0 = xf;
this.cy0 = yf;
this.cx0 = x3;
this.cy0 = y3;
this.cdx = dxf;
this.cdy = dyf;
this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;
@ -1189,74 +1205,101 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
final double x2, final double y2)
{
final int outcode0 = this.cOutCode;
if (clipRect != null) {
final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
this.cOutCode = outcode2;
if ((outcode0 & outcode2) != 0) {
final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
// Should clip
final int orCode = (outcode0 | outcode1 | outcode2);
if (orCode != 0) {
final int sideCode = outcode0 & outcode1 & outcode2;
// basic rejection criteria
if ((outcode0 & outcode1 & outcode2) != 0) {
moveTo(x2, y2, outcode0);
// basic rejection criteria:
if (sideCode == 0) {
// ovelap clip:
if (subdivide) {
// avoid reentrance
subdivide = false;
// subdivide curve => call lineTo() with subdivided curves:
boolean ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,
x2, y2, orCode, this);
// reentrance is done:
subdivide = true;
if (ret) {
return;
}
}
// already subdivided so render it
} else {
this.cOutCode = outcode2;
_moveTo(x2, y2, outcode0);
opened = true;
return;
}
}
this.cOutCode = outcode2;
}
_quadTo(x1, y1, x2, y2, outcode0);
}
final double[] mid = middle;
mid[0] = cx0; mid[1] = cy0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
private void _quadTo(final double x1, final double y1,
final double x2, final double y2,
final int outcode0)
{
// need these so we can update the state at the end of this method
final double xf = x2, yf = y2;
double dxs = mid[2] - mid[0];
double dys = mid[3] - mid[1];
double dxf = mid[4] - mid[2];
double dyf = mid[5] - mid[3];
if ((dxs == 0.0d && dys == 0.0d) || (dxf == 0.0d && dyf == 0.0d)) {
dxs = dxf = mid[4] - mid[0];
dys = dyf = mid[5] - mid[1];
double dxs = x1 - cx0;
double dys = y1 - cy0;
double dxf = x2 - x1;
double dyf = y2 - y1;
if (((dxs == 0.0d) && (dys == 0.0d)) || ((dxf == 0.0d) && (dyf == 0.0d))) {
dxs = dxf = x2 - cx0;
dys = dyf = y2 - cy0;
}
if (dxs == 0.0d && dys == 0.0d) {
if ((dxs == 0.0d) && (dys == 0.0d)) {
// this happens if the "curve" is just a point
// fix outcode0 for lineTo() call:
if (clipRect != null) {
this.cOutCode = outcode0;
}
lineTo(mid[0], mid[1]);
lineTo(cx0, cy0);
return;
}
// if these vectors are too small, normalize them, to avoid future
// precision problems.
if (Math.abs(dxs) < 0.1d && Math.abs(dys) < 0.1d) {
double len = Math.sqrt(dxs*dxs + dys*dys);
final double len = Math.sqrt(dxs * dxs + dys * dys);
dxs /= len;
dys /= len;
}
if (Math.abs(dxf) < 0.1d && Math.abs(dyf) < 0.1d) {
double len = Math.sqrt(dxf*dxf + dyf*dyf);
final double len = Math.sqrt(dxf * dxf + dyf * dyf);
dxf /= len;
dyf /= len;
}
computeOffset(dxs, dys, lineWidth2, offset0);
drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);
double prevt = 0.0d;
for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
final double t = subdivTs[i];
DHelpers.subdivideQuadAt((t - prevt) / (1.0d - prevt),
mid, off, mid, off, mid, off + 4);
prevt = t;
}
int nSplits = 0;
final double[] mid;
final double[] l = lp;
if (monotonize) {
// monotonize quad:
final CurveBasicMonotonizer monotonizer
= rdrCtx.monotonizer.quad(cx0, cy0, x1, y1, x2, y2);
nSplits = monotonizer.nbSplits;
mid = monotonizer.middle;
} else {
// use left instead:
mid = l;
mid[0] = cx0; mid[1] = cy0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
}
final double[] r = rp;
int kind = 0;
@ -1280,8 +1323,8 @@ final class DStroker implements DPathConsumer2D, MarlinConst {
}
this.prev = DRAWING_OP_TO;
this.cx0 = xf;
this.cy0 = yf;
this.cx0 = x2;
this.cy0 = y2;
this.cdx = dxf;
this.cdy = dyf;
this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0d;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2018, 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
@ -27,11 +27,15 @@ package sun.java2d.marlin;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.util.Arrays;
import sun.java2d.marlin.DHelpers.IndexStack;
import sun.java2d.marlin.DHelpers.PolyStack;
final class DTransformingPathConsumer2D {
// smaller uncertainty in double variant
static final double CLIP_RECT_PADDING = 0.25d;
private final DRendererContext rdrCtx;
// recycled ClosedPathDetector instance from detectClosedPath()
@ -56,6 +60,7 @@ final class DTransformingPathConsumer2D {
private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
private final PathTracer tracerFiller = new PathTracer("Filler");
private final PathTracer tracerStroker = new PathTracer("Stroker");
private final PathTracer tracerDasher = new PathTracer("Dasher");
DTransformingPathConsumer2D(final DRendererContext rdrCtx) {
// used by RendererContext
@ -84,6 +89,10 @@ final class DTransformingPathConsumer2D {
return tracerStroker.init(out);
}
DPathConsumer2D traceDasher(DPathConsumer2D out) {
return tracerDasher.init(out);
}
DPathConsumer2D detectClosedPath(DPathConsumer2D out) {
return cpDetector.init(out);
}
@ -499,11 +508,19 @@ final class DTransformingPathConsumer2D {
private boolean outside = false;
// The current point OUTSIDE
// The current point (TODO stupid repeated info)
private double cx0, cy0;
// The current point OUTSIDE
private double cox0, coy0;
private boolean subdivide = MarlinConst.DO_CLIP_SUBDIVIDER;
private final CurveClipSplitter curveSplitter;
PathClipFilter(final DRendererContext rdrCtx) {
this.clipRect = rdrCtx.clipRect;
this.curveSplitter = rdrCtx.curveClipSplitter;
this.stack = (rdrCtx.stats != null) ?
new IndexStack(rdrCtx,
rdrCtx.stats.stat_pcf_idxstack_indices,
@ -528,6 +545,11 @@ final class DTransformingPathConsumer2D {
_clipRect[2] -= margin - rdrOffX;
_clipRect[3] += margin + rdrOffX;
if (MarlinConst.DO_CLIP_SUBDIVIDER) {
// adjust padded clip rectangle:
curveSplitter.init();
}
this.init_corners = true;
this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
@ -578,7 +600,9 @@ final class DTransformingPathConsumer2D {
}
stack.pullAll(corners, out);
}
out.lineTo(cx0, cy0);
out.lineTo(cox0, coy0);
this.cx0 = cox0;
this.cy0 = coy0;
}
@Override
@ -603,38 +627,68 @@ final class DTransformingPathConsumer2D {
public void moveTo(final double x0, final double y0) {
finishPath();
final int outcode = DHelpers.outcode(x0, y0, clipRect);
this.cOutCode = outcode;
this.cOutCode = DHelpers.outcode(x0, y0, clipRect);
this.outside = false;
out.moveTo(x0, y0);
this.cx0 = x0;
this.cy0 = y0;
}
@Override
public void lineTo(final double xe, final double ye) {
final int outcode0 = this.cOutCode;
final int outcode1 = DHelpers.outcode(xe, ye, clipRect);
this.cOutCode = outcode1;
final int sideCode = (outcode0 & outcode1);
// Should clip
final int orCode = (outcode0 | outcode1);
if (orCode != 0) {
final int sideCode = (outcode0 & outcode1);
// basic rejection criteria:
if (sideCode == 0) {
this.gOutCode = 0;
} else {
this.gOutCode &= sideCode;
// keep last point coordinate before entering the clip again:
this.outside = true;
this.cx0 = xe;
this.cy0 = ye;
// basic rejection criteria:
if (sideCode == 0) {
// ovelap clip:
if (subdivide) {
// avoid reentrance
subdivide = false;
boolean ret;
// subdivide curve => callback with subdivided parts:
if (outside) {
ret = curveSplitter.splitLine(cox0, coy0, xe, ye,
orCode, this);
} else {
ret = curveSplitter.splitLine(cx0, cy0, xe, ye,
orCode, this);
}
// reentrance is done:
subdivide = true;
if (ret) {
return;
}
}
// already subdivided so render it
} else {
this.cOutCode = outcode1;
this.gOutCode &= sideCode;
// keep last point coordinate before entering the clip again:
this.outside = true;
this.cox0 = xe;
this.coy0 = ye;
clip(sideCode, outcode0, outcode1);
return;
clip(sideCode, outcode0, outcode1);
return;
}
}
this.cOutCode = outcode1;
this.gOutCode = 0;
if (outside) {
finish();
}
// clipping disabled:
out.lineTo(xe, ye);
this.cx0 = xe;
this.cy0 = ye;
}
private void clip(final int sideCode,
@ -654,22 +708,18 @@ final class DTransformingPathConsumer2D {
// add corners to outside stack:
switch (tbCode) {
case MarlinConst.OUTCODE_TOP:
// System.out.println("TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
stack.push(off); // top
return;
case MarlinConst.OUTCODE_BOTTOM:
// System.out.println("BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
stack.push(off + 1); // bottom
return;
default:
// both TOP / BOTTOM:
if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {
// System.out.println("TOP + BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
// top to bottom
stack.push(off); // top
stack.push(off + 1); // bottom
} else {
// System.out.println("BOTTOM + TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
// bottom to top
stack.push(off + 1); // bottom
stack.push(off); // top
@ -684,34 +734,62 @@ final class DTransformingPathConsumer2D {
final double xe, final double ye)
{
final int outcode0 = this.cOutCode;
final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
final int outcode2 = DHelpers.outcode(x2, y2, clipRect);
final int outcode3 = DHelpers.outcode(xe, ye, clipRect);
this.cOutCode = outcode3;
int sideCode = outcode0 & outcode3;
if (sideCode == 0) {
this.gOutCode = 0;
} else {
sideCode &= DHelpers.outcode(x1, y1, clipRect);
sideCode &= DHelpers.outcode(x2, y2, clipRect);
this.gOutCode &= sideCode;
// Should clip
final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);
if (orCode != 0) {
final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;
// basic rejection criteria:
if (sideCode != 0) {
if (sideCode == 0) {
// ovelap clip:
if (subdivide) {
// avoid reentrance
subdivide = false;
// subdivide curve => callback with subdivided parts:
boolean ret;
if (outside) {
ret = curveSplitter.splitCurve(cox0, coy0, x1, y1,
x2, y2, xe, ye,
orCode, this);
} else {
ret = curveSplitter.splitCurve(cx0, cy0, x1, y1,
x2, y2, xe, ye,
orCode, this);
}
// reentrance is done:
subdivide = true;
if (ret) {
return;
}
}
// already subdivided so render it
} else {
this.cOutCode = outcode3;
this.gOutCode &= sideCode;
// keep last point coordinate before entering the clip again:
this.outside = true;
this.cx0 = xe;
this.cy0 = ye;
this.cox0 = xe;
this.coy0 = ye;
clip(sideCode, outcode0, outcode3);
return;
}
}
this.cOutCode = outcode3;
this.gOutCode = 0;
if (outside) {
finish();
}
// clipping disabled:
out.curveTo(x1, y1, x2, y2, xe, ye);
this.cx0 = xe;
this.cy0 = ye;
}
@Override
@ -719,33 +797,59 @@ final class DTransformingPathConsumer2D {
final double xe, final double ye)
{
final int outcode0 = this.cOutCode;
final int outcode1 = DHelpers.outcode(x1, y1, clipRect);
final int outcode2 = DHelpers.outcode(xe, ye, clipRect);
this.cOutCode = outcode2;
int sideCode = outcode0 & outcode2;
if (sideCode == 0) {
this.gOutCode = 0;
} else {
sideCode &= DHelpers.outcode(x1, y1, clipRect);
this.gOutCode &= sideCode;
// Should clip
final int orCode = (outcode0 | outcode1 | outcode2);
if (orCode != 0) {
final int sideCode = outcode0 & outcode1 & outcode2;
// basic rejection criteria:
if (sideCode != 0) {
if (sideCode == 0) {
// ovelap clip:
if (subdivide) {
// avoid reentrance
subdivide = false;
// subdivide curve => callback with subdivided parts:
boolean ret;
if (outside) {
ret = curveSplitter.splitQuad(cox0, coy0, x1, y1,
xe, ye, orCode, this);
} else {
ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,
xe, ye, orCode, this);
}
// reentrance is done:
subdivide = true;
if (ret) {
return;
}
}
// already subdivided so render it
} else {
this.cOutCode = outcode2;
this.gOutCode &= sideCode;
// keep last point coordinate before entering the clip again:
this.outside = true;
this.cx0 = xe;
this.cy0 = ye;
this.cox0 = xe;
this.coy0 = ye;
clip(sideCode, outcode0, outcode2);
return;
}
}
this.cOutCode = outcode2;
this.gOutCode = 0;
if (outside) {
finish();
}
// clipping disabled:
out.quadTo(x1, y1, xe, ye);
this.cx0 = xe;
this.cy0 = ye;
}
@Override
@ -754,6 +858,261 @@ final class DTransformingPathConsumer2D {
}
}
static final class CurveClipSplitter {
static final double LEN_TH = MarlinProperties.getSubdividerMinLength();
static final boolean DO_CHECK_LENGTH = (LEN_TH > 0.0d);
private static final boolean TRACE = false;
private static final int MAX_N_CURVES = 3 * 4;
// clip rectangle (ymin, ymax, xmin, xmax):
final double[] clipRect;
// clip rectangle (ymin, ymax, xmin, xmax) including padding:
final double[] clipRectPad = new double[4];
private boolean init_clipRectPad = false;
// This is where the curve to be processed is put. We give it
// enough room to store all curves.
final double[] middle = new double[MAX_N_CURVES * 8 + 2];
// t values at subdivision points
private final double[] subdivTs = new double[MAX_N_CURVES];
// dirty curve
private final DCurve curve;
CurveClipSplitter(final DRendererContext rdrCtx) {
this.clipRect = rdrCtx.clipRect;
this.curve = rdrCtx.curve;
}
void init() {
this.init_clipRectPad = true;
}
private void initPaddedClip() {
// bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
// adjust padded clip rectangle (ymin, ymax, xmin, xmax):
// add a rounding error (curve subdivision ~ 0.1px):
final double[] _clipRect = clipRect;
final double[] _clipRectPad = clipRectPad;
_clipRectPad[0] = _clipRect[0] - CLIP_RECT_PADDING;
_clipRectPad[1] = _clipRect[1] + CLIP_RECT_PADDING;
_clipRectPad[2] = _clipRect[2] - CLIP_RECT_PADDING;
_clipRectPad[3] = _clipRect[3] + CLIP_RECT_PADDING;
if (TRACE) {
MarlinUtils.logInfo("clip: X [" + _clipRectPad[2] + " .. " + _clipRectPad[3] +"] "
+ "Y ["+ _clipRectPad[0] + " .. " + _clipRectPad[1] +"]");
}
}
boolean splitLine(final double x0, final double y0,
final double x1, final double y1,
final int outCodeOR,
final DPathConsumer2D out)
{
if (TRACE) {
MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")");
}
if (DO_CHECK_LENGTH && DHelpers.fastLineLen(x0, y0, x1, y1) <= LEN_TH) {
return false;
}
final double[] mid = middle;
mid[0] = x0; mid[1] = y0;
mid[2] = x1; mid[3] = y1;
return subdivideAtIntersections(4, outCodeOR, out);
}
boolean splitQuad(final double x0, final double y0,
final double x1, final double y1,
final double x2, final double y2,
final int outCodeOR,
final DPathConsumer2D out)
{
if (TRACE) {
MarlinUtils.logInfo("divQuad P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ")");
}
if (DO_CHECK_LENGTH && DHelpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= LEN_TH) {
return false;
}
final double[] mid = middle;
mid[0] = x0; mid[1] = y0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
return subdivideAtIntersections(6, outCodeOR, out);
}
boolean splitCurve(final double x0, final double y0,
final double x1, final double y1,
final double x2, final double y2,
final double x3, final double y3,
final int outCodeOR,
final DPathConsumer2D out)
{
if (TRACE) {
MarlinUtils.logInfo("divCurve P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ")");
}
if (DO_CHECK_LENGTH && DHelpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= LEN_TH) {
return false;
}
final double[] mid = middle;
mid[0] = x0; mid[1] = y0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
mid[6] = x3; mid[7] = y3;
return subdivideAtIntersections(8, outCodeOR, out);
}
private boolean subdivideAtIntersections(final int type, final int outCodeOR,
final DPathConsumer2D out)
{
final double[] mid = middle;
final double[] subTs = subdivTs;
if (init_clipRectPad) {
init_clipRectPad = false;
initPaddedClip();
}
final int nSplits = DHelpers.findClipPoints(curve, mid, subTs, type,
outCodeOR, clipRectPad);
if (TRACE) {
MarlinUtils.logInfo("nSplits: "+ nSplits);
MarlinUtils.logInfo("subTs: "+Arrays.toString(Arrays.copyOfRange(subTs, 0, nSplits)));
}
if (nSplits == 0) {
// only curve support shortcut
return false;
}
double prevT = 0.0d;
for (int i = 0, off = 0; i < nSplits; i++, off += type) {
final double t = subTs[i];
DHelpers.subdivideAt((t - prevT) / (1.0d - prevT),
mid, off, mid, off, type);
prevT = t;
}
for (int i = 0, off = 0; i <= nSplits; i++, off += type) {
if (TRACE) {
MarlinUtils.logInfo("Part Curve "+Arrays.toString(Arrays.copyOfRange(mid, off, off + type)));
}
emitCurrent(type, mid, off, out);
}
return true;
}
static void emitCurrent(final int type, final double[] pts,
final int off, final DPathConsumer2D out)
{
// if instead of switch (perf + most probable cases first)
if (type == 8) {
out.curveTo(pts[off + 2], pts[off + 3],
pts[off + 4], pts[off + 5],
pts[off + 6], pts[off + 7]);
} else if (type == 4) {
out.lineTo(pts[off + 2], pts[off + 3]);
} else {
out.quadTo(pts[off + 2], pts[off + 3],
pts[off + 4], pts[off + 5]);
}
}
}
static final class CurveBasicMonotonizer {
private static final int MAX_N_CURVES = 11;
// squared half line width (for stroker)
private double lw2;
// number of splitted curves
int nbSplits;
// This is where the curve to be processed is put. We give it
// enough room to store all curves.
final double[] middle = new double[MAX_N_CURVES * 6 + 2];
// t values at subdivision points
private final double[] subdivTs = new double[MAX_N_CURVES - 1];
// dirty curve
private final DCurve curve;
CurveBasicMonotonizer(final DRendererContext rdrCtx) {
this.curve = rdrCtx.curve;
}
void init(final double lineWidth) {
this.lw2 = (lineWidth * lineWidth) / 4.0d;
}
CurveBasicMonotonizer curve(final double x0, final double y0,
final double x1, final double y1,
final double x2, final double y2,
final double x3, final double y3)
{
final double[] mid = middle;
mid[0] = x0; mid[1] = y0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
mid[6] = x3; mid[7] = y3;
final double[] subTs = subdivTs;
final int nSplits = DHelpers.findSubdivPoints(curve, mid, subTs, 8, lw2);
double prevT = 0.0d;
for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
final double t = subTs[i];
DHelpers.subdivideCubicAt((t - prevT) / (1.0d - prevT),
mid, off, mid, off, off + 6);
prevT = t;
}
this.nbSplits = nSplits;
return this;
}
CurveBasicMonotonizer quad(final double x0, final double y0,
final double x1, final double y1,
final double x2, final double y2)
{
final double[] mid = middle;
mid[0] = x0; mid[1] = y0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
final double[] subTs = subdivTs;
final int nSplits = DHelpers.findSubdivPoints(curve, mid, subTs, 6, lw2);
double prevt = 0.0d;
for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
final double t = subTs[i];
DHelpers.subdivideQuadAt((t - prevt) / (1.0d - prevt),
mid, off, mid, off, off + 4);
prevt = t;
}
this.nbSplits = nSplits;
return this;
}
}
static final class PathTracer implements DPathConsumer2D {
private final String prefix;
private DPathConsumer2D out;
@ -807,7 +1166,7 @@ final class DTransformingPathConsumer2D {
}
private void log(final String message) {
System.out.println(prefix + message);
MarlinUtils.logInfo(prefix + message);
}
@Override

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2018, 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
@ -27,6 +27,8 @@ package sun.java2d.marlin;
import java.util.Arrays;
import sun.awt.geom.PathConsumer2D;
import sun.java2d.marlin.TransformingPathConsumer2D.CurveBasicMonotonizer;
import sun.java2d.marlin.TransformingPathConsumer2D.CurveClipSplitter;
/**
* The <code>Dasher</code> class takes a series of linear commands
@ -41,8 +43,9 @@ import sun.awt.geom.PathConsumer2D;
*/
final class Dasher implements PathConsumer2D, MarlinConst {
static final int REC_LIMIT = 4;
static final float ERR = 0.01f;
/* huge circle with radius ~ 2E9 only needs 12 subdivision levels */
static final int REC_LIMIT = 16;
static final float CURVE_LEN_ERR = MarlinProperties.getCurveLengthError(); // 0.01
static final float MIN_T_INC = 1.0f / (1 << REC_LIMIT);
// More than 24 bits of mantissa means we can no longer accurately
@ -64,8 +67,10 @@ final class Dasher implements PathConsumer2D, MarlinConst {
private boolean dashOn;
private float phase;
private float sx, sy;
private float x0, y0;
// The starting point of the path
private float sx0, sy0;
// the current point
private float cx0, cy0;
// temporary storage for the current curve
private final float[] curCurvepts;
@ -76,11 +81,34 @@ final class Dasher implements PathConsumer2D, MarlinConst {
// flag to recycle dash array copy
boolean recycleDashes;
// We don't emit the first dash right away. If we did, caps would be
// drawn on it, but we need joins to be drawn if there's a closePath()
// So, we store the path elements that make up the first dash in the
// buffer below.
private float[] firstSegmentsBuffer; // dynamic array
private int firstSegidx;
// dashes ref (dirty)
final FloatArrayCache.Reference dashes_ref;
// firstSegmentsBuffer ref (dirty)
final FloatArrayCache.Reference firstSegmentsBuffer_ref;
// Bounds of the drawing region, at pixel precision.
private float[] clipRect;
// the outcode of the current point
private int cOutCode = 0;
private boolean subdivide = DO_CLIP_SUBDIVIDER;
private final LengthIterator li = new LengthIterator();
private final CurveClipSplitter curveSplitter;
private float cycleLen;
private boolean outside;
private float totalSkipLen;
/**
* Constructs a <code>Dasher</code>.
* @param rdrCtx per-thread renderer context
@ -96,6 +124,8 @@ final class Dasher implements PathConsumer2D, MarlinConst {
// we need curCurvepts to be able to contain 2 curves because when
// dashing curves, we need to subdivide it
curCurvepts = new float[8 * 2];
this.curveSplitter = rdrCtx.curveClipSplitter;
}
/**
@ -116,10 +146,13 @@ final class Dasher implements PathConsumer2D, MarlinConst {
// Normalize so 0 <= phase < dash[0]
int sidx = 0;
dashOn = true;
float sum = 0.0f;
for (float d : dash) {
sum += d;
}
this.cycleLen = sum;
float cycles = phase / sum;
if (phase < 0.0f) {
if (-cycles >= MAX_CYCLES) {
@ -168,6 +201,12 @@ final class Dasher implements PathConsumer2D, MarlinConst {
this.recycleDashes = recycleDashes;
if (rdrCtx.doClip) {
this.clipRect = rdrCtx.clipRect;
} else {
this.clipRect = null;
this.cOutCode = 0;
}
return this; // fluent API
}
@ -205,33 +244,42 @@ final class Dasher implements PathConsumer2D, MarlinConst {
@Override
public void moveTo(final float x0, final float y0) {
if (firstSegidx != 0) {
out.moveTo(sx, sy);
out.moveTo(sx0, sy0);
emitFirstSegments();
}
needsMoveTo = true;
this.needsMoveTo = true;
this.idx = startIdx;
this.dashOn = this.startDashOn;
this.phase = this.startPhase;
this.sx = x0;
this.sy = y0;
this.x0 = x0;
this.y0 = y0;
this.cx0 = x0;
this.cy0 = y0;
// update starting point:
this.sx0 = x0;
this.sy0 = y0;
this.starting = true;
if (clipRect != null) {
final int outcode = Helpers.outcode(x0, y0, clipRect);
this.cOutCode = outcode;
this.outside = false;
this.totalSkipLen = 0.0f;
}
}
private void emitSeg(float[] buf, int off, int type) {
switch (type) {
case 8:
out.curveTo(buf[off+0], buf[off+1],
buf[off+2], buf[off+3],
buf[off+4], buf[off+5]);
out.curveTo(buf[off ], buf[off + 1],
buf[off + 2], buf[off + 3],
buf[off + 4], buf[off + 5]);
return;
case 6:
out.quadTo(buf[off+0], buf[off+1],
buf[off+2], buf[off+3]);
out.quadTo(buf[off ], buf[off + 1],
buf[off + 2], buf[off + 3]);
return;
case 4:
out.lineTo(buf[off], buf[off+1]);
out.lineTo(buf[off], buf[off + 1]);
return;
default:
}
@ -247,12 +295,6 @@ final class Dasher implements PathConsumer2D, MarlinConst {
}
firstSegidx = 0;
}
// We don't emit the first dash right away. If we did, caps would be
// drawn on it, but we need joins to be drawn if there's a closePath()
// So, we store the path elements that make up the first dash in the
// buffer below.
private float[] firstSegmentsBuffer; // dynamic array
private int firstSegidx;
// precondition: pts must be in relative coordinates (relative to x0,y0)
private void goTo(final float[] pts, final int off, final int type,
@ -268,7 +310,7 @@ final class Dasher implements PathConsumer2D, MarlinConst {
} else {
if (needsMoveTo) {
needsMoveTo = false;
out.moveTo(x0, y0);
out.moveTo(cx0, cy0);
}
emitSeg(pts, off, type);
}
@ -279,8 +321,8 @@ final class Dasher implements PathConsumer2D, MarlinConst {
}
needsMoveTo = true;
}
this.x0 = x;
this.y0 = y;
this.cx0 = x;
this.cy0 = y;
}
private void goTo_starting(final float[] pts, final int off, final int type) {
@ -306,10 +348,56 @@ final class Dasher implements PathConsumer2D, MarlinConst {
@Override
public void lineTo(final float x1, final float y1) {
final float dx = x1 - x0;
final float dy = y1 - y0;
final int outcode0 = this.cOutCode;
float len = dx*dx + dy*dy;
if (clipRect != null) {
final int outcode1 = Helpers.outcode(x1, y1, clipRect);
// Should clip
final int orCode = (outcode0 | outcode1);
if (orCode != 0) {
final int sideCode = outcode0 & outcode1;
// basic rejection criteria:
if (sideCode == 0) {
// ovelap clip:
if (subdivide) {
// avoid reentrance
subdivide = false;
// subdivide curve => callback with subdivided parts:
boolean ret = curveSplitter.splitLine(cx0, cy0, x1, y1,
orCode, this);
// reentrance is done:
subdivide = true;
if (ret) {
return;
}
}
// already subdivided so render it
} else {
this.cOutCode = outcode1;
skipLineTo(x1, y1);
return;
}
}
this.cOutCode = outcode1;
if (this.outside) {
this.outside = false;
// Adjust current index, phase & dash:
skipLen();
}
}
_lineTo(x1, y1);
}
private void _lineTo(final float x1, final float y1) {
final float dx = x1 - cx0;
final float dy = y1 - cy0;
float len = dx * dx + dy * dy;
if (len == 0.0f) {
return;
}
@ -328,8 +416,7 @@ final class Dasher implements PathConsumer2D, MarlinConst {
boolean _dashOn = dashOn;
float _phase = phase;
float leftInThisDashSegment;
float d, dashdx, dashdy, p;
float leftInThisDashSegment, d;
while (true) {
d = _dash[_idx];
@ -350,24 +437,15 @@ final class Dasher implements PathConsumer2D, MarlinConst {
_idx = (_idx + 1) % _dashLen;
_dashOn = !_dashOn;
}
// Save local state:
idx = _idx;
dashOn = _dashOn;
phase = _phase;
return;
break;
}
dashdx = d * cx;
dashdy = d * cy;
if (_phase == 0.0f) {
_curCurvepts[0] = x0 + dashdx;
_curCurvepts[1] = y0 + dashdy;
_curCurvepts[0] = cx0 + d * cx;
_curCurvepts[1] = cy0 + d * cy;
} else {
p = leftInThisDashSegment / d;
_curCurvepts[0] = x0 + p * dashdx;
_curCurvepts[1] = y0 + p * dashdy;
_curCurvepts[0] = cx0 + leftInThisDashSegment * cx;
_curCurvepts[1] = cy0 + leftInThisDashSegment * cy;
}
goTo(_curCurvepts, 0, 4, _dashOn);
@ -378,19 +456,95 @@ final class Dasher implements PathConsumer2D, MarlinConst {
_dashOn = !_dashOn;
_phase = 0.0f;
}
// Save local state:
idx = _idx;
dashOn = _dashOn;
phase = _phase;
}
// shared instance in Dasher
private final LengthIterator li = new LengthIterator();
private void skipLineTo(final float x1, final float y1) {
final float dx = x1 - cx0;
final float dy = y1 - cy0;
float len = dx * dx + dy * dy;
if (len != 0.0f) {
len = (float)Math.sqrt(len);
}
// Accumulate skipped length:
this.outside = true;
this.totalSkipLen += len;
// Fix initial move:
this.needsMoveTo = true;
this.starting = false;
this.cx0 = x1;
this.cy0 = y1;
}
public void skipLen() {
float len = this.totalSkipLen;
this.totalSkipLen = 0.0f;
final float[] _dash = dash;
final int _dashLen = this.dashLen;
int _idx = idx;
boolean _dashOn = dashOn;
float _phase = phase;
// -2 to ensure having 2 iterations of the post-loop
// to compensate the remaining phase
final long fullcycles = (long)Math.floor(len / cycleLen) - 2L;
if (fullcycles > 0L) {
len -= cycleLen * fullcycles;
final long iterations = fullcycles * _dashLen;
_idx = (int) (iterations + _idx) % _dashLen;
_dashOn = (iterations + (_dashOn ? 1L : 0L) & 1L) == 1L;
}
float leftInThisDashSegment, d;
while (true) {
d = _dash[_idx];
leftInThisDashSegment = d - _phase;
if (len <= leftInThisDashSegment) {
// Advance phase within current dash segment
_phase += len;
// TODO: compare float values using epsilon:
if (len == leftInThisDashSegment) {
_phase = 0.0f;
_idx = (_idx + 1) % _dashLen;
_dashOn = !_dashOn;
}
break;
}
len -= leftInThisDashSegment;
// Advance to next dash segment
_idx = (_idx + 1) % _dashLen;
_dashOn = !_dashOn;
_phase = 0.0f;
}
// Save local state:
idx = _idx;
dashOn = _dashOn;
phase = _phase;
}
// preconditions: curCurvepts must be an array of length at least 2 * type,
// that contains the curve we want to dash in the first type elements
private void somethingTo(final int type) {
if (pointCurve(curCurvepts, type)) {
final float[] _curCurvepts = curCurvepts;
if (pointCurve(_curCurvepts, type)) {
return;
}
final LengthIterator _li = li;
final float[] _curCurvepts = curCurvepts;
final float[] _dash = dash;
final int _dashLen = this.dashLen;
@ -402,17 +556,16 @@ final class Dasher implements PathConsumer2D, MarlinConst {
// initially the current curve is at curCurvepts[0...type]
int curCurveoff = 0;
float lastSplitT = 0.0f;
float prevT = 0.0f;
float t;
float leftInThisDashSegment = _dash[_idx] - _phase;
while ((t = _li.next(leftInThisDashSegment)) < 1.0f) {
if (t != 0.0f) {
Helpers.subdivideAt((t - lastSplitT) / (1.0f - lastSplitT),
Helpers.subdivideAt((t - prevT) / (1.0f - prevT),
_curCurvepts, curCurveoff,
_curCurvepts, 0,
_curCurvepts, type, type);
lastSplitT = t;
_curCurvepts, 0, type);
prevT = t;
goTo(_curCurvepts, 2, type, _dashOn);
curCurveoff = type;
}
@ -440,7 +593,29 @@ final class Dasher implements PathConsumer2D, MarlinConst {
_li.reset();
}
private static boolean pointCurve(float[] curve, int type) {
private void skipSomethingTo(final int type) {
final float[] _curCurvepts = curCurvepts;
if (pointCurve(_curCurvepts, type)) {
return;
}
final LengthIterator _li = li;
_li.initializeIterationOnCurve(_curCurvepts, type);
// In contrary to somethingTo(),
// just estimate properly the curve length:
final float len = _li.totalLength();
// Accumulate skipped length:
this.outside = true;
this.totalSkipLen += len;
// Fix initial move:
this.needsMoveTo = true;
this.starting = false;
}
private static boolean pointCurve(final float[] curve, final int type) {
for (int i = 2; i < type; i++) {
if (curve[i] != curve[i-2]) {
return false;
@ -463,15 +638,14 @@ final class Dasher implements PathConsumer2D, MarlinConst {
// tree; however, the trees we are interested in have the property that
// every non leaf node has exactly 2 children
static final class LengthIterator {
private enum Side {LEFT, RIGHT}
// Holds the curves at various levels of the recursion. The root
// (i.e. the original curve) is at recCurveStack[0] (but then it
// gets subdivided, the left half is put at 1, so most of the time
// only the right half of the original curve is at 0)
private final float[][] recCurveStack; // dirty
// sides[i] indicates whether the node at level i+1 in the path from
// sidesRight[i] indicates whether the node at level i+1 in the path from
// the root to the current leaf is a left or right child of its parent.
private final Side[] sides; // dirty
private final boolean[] sidesRight; // dirty
private int curveType;
// lastT and nextT delimit the current leaf.
private float nextT;
@ -492,7 +666,7 @@ final class Dasher implements PathConsumer2D, MarlinConst {
LengthIterator() {
this.recCurveStack = new float[REC_LIMIT + 1][8];
this.sides = new Side[REC_LIMIT];
this.sidesRight = new boolean[REC_LIMIT];
// if any methods are called without first initializing this object
// on a curve, we want it to fail ASAP.
this.nextT = Float.MAX_VALUE;
@ -514,7 +688,7 @@ final class Dasher implements PathConsumer2D, MarlinConst {
for (int i = recLimit; i >= 0; i--) {
Arrays.fill(recCurveStack[i], 0.0f);
}
Arrays.fill(sides, Side.LEFT);
Arrays.fill(sidesRight, false);
Arrays.fill(curLeafCtrlPolyLengths, 0.0f);
Arrays.fill(nextRoots, 0.0f);
Arrays.fill(flatLeafCoefCache, 0.0f);
@ -522,7 +696,7 @@ final class Dasher implements PathConsumer2D, MarlinConst {
}
}
void initializeIterationOnCurve(float[] pts, int type) {
void initializeIterationOnCurve(final float[] pts, final int type) {
// optimize arraycopy (8 values faster than 6 = type):
System.arraycopy(pts, 0, recCurveStack[0], 0, 8);
this.curveType = type;
@ -534,11 +708,11 @@ final class Dasher implements PathConsumer2D, MarlinConst {
goLeft(); // initializes nextT and lenAtNextT properly
this.lenAtLastSplit = 0.0f;
if (recLevel > 0) {
this.sides[0] = Side.LEFT;
this.sidesRight[0] = false;
this.done = false;
} else {
// the root of the tree is a leaf so we're done.
this.sides[0] = Side.RIGHT;
this.sidesRight[0] = true;
this.done = true;
}
this.lastSegLen = 0.0f;
@ -547,7 +721,7 @@ final class Dasher implements PathConsumer2D, MarlinConst {
// 0 == false, 1 == true, -1 == invalid cached value.
private int cachedHaveLowAcceleration = -1;
private boolean haveLowAcceleration(float err) {
private boolean haveLowAcceleration(final float err) {
if (cachedHaveLowAcceleration == -1) {
final float len1 = curLeafCtrlPolyLengths[0];
final float len2 = curLeafCtrlPolyLengths[1];
@ -636,7 +810,7 @@ final class Dasher implements PathConsumer2D, MarlinConst {
// we use cubicRootsInAB here, because we want only roots in 0, 1,
// and our quadratic root finder doesn't filter, so it's just a
// matter of convenience.
int n = Helpers.cubicRootsInAB(a, b, c, d, nextRoots, 0, 0.0f, 1.0f);
final int n = Helpers.cubicRootsInAB(a, b, c, d, nextRoots, 0, 0.0f, 1.0f);
if (n == 1 && !Float.isNaN(nextRoots[0])) {
t = nextRoots[0];
}
@ -657,6 +831,16 @@ final class Dasher implements PathConsumer2D, MarlinConst {
return t;
}
float totalLength() {
while (!done) {
goToNextLeaf();
}
// reset LengthIterator:
reset();
return lenAtNextT;
}
float lastSegLen() {
return lastSegLen;
}
@ -666,11 +850,11 @@ final class Dasher implements PathConsumer2D, MarlinConst {
private void goToNextLeaf() {
// We must go to the first ancestor node that has an unvisited
// right child.
final boolean[] _sides = sidesRight;
int _recLevel = recLevel;
final Side[] _sides = sides;
_recLevel--;
while(_sides[_recLevel] == Side.RIGHT) {
while(_sides[_recLevel]) {
if (_recLevel == 0) {
recLevel = 0;
done = true;
@ -679,19 +863,17 @@ final class Dasher implements PathConsumer2D, MarlinConst {
_recLevel--;
}
_sides[_recLevel] = Side.RIGHT;
_sides[_recLevel] = true;
// optimize arraycopy (8 values faster than 6 = type):
System.arraycopy(recCurveStack[_recLevel], 0,
recCurveStack[_recLevel+1], 0, 8);
_recLevel++;
System.arraycopy(recCurveStack[_recLevel++], 0,
recCurveStack[_recLevel], 0, 8);
recLevel = _recLevel;
goLeft();
}
// go to the leftmost node from the current node. Return its length.
private void goLeft() {
float len = onLeaf();
final float len = onLeaf();
if (len >= 0.0f) {
lastT = nextT;
lenAtLastT = lenAtNextT;
@ -701,10 +883,11 @@ final class Dasher implements PathConsumer2D, MarlinConst {
flatLeafCoefCache[2] = -1.0f;
cachedHaveLowAcceleration = -1;
} else {
Helpers.subdivide(recCurveStack[recLevel], 0,
recCurveStack[recLevel+1], 0,
recCurveStack[recLevel], 0, curveType);
sides[recLevel] = Side.LEFT;
Helpers.subdivide(recCurveStack[recLevel],
recCurveStack[recLevel + 1],
recCurveStack[recLevel], curveType);
sidesRight[recLevel] = false;
recLevel++;
goLeft();
}
@ -719,7 +902,7 @@ final class Dasher implements PathConsumer2D, MarlinConst {
float x0 = curve[0], y0 = curve[1];
for (int i = 2; i < _curveType; i += 2) {
final float x1 = curve[i], y1 = curve[i+1];
final float x1 = curve[i], y1 = curve[i + 1];
final float len = Helpers.linelen(x0, y0, x1, y1);
polyLen += len;
curLeafCtrlPolyLengths[(i >> 1) - 1] = len;
@ -727,10 +910,9 @@ final class Dasher implements PathConsumer2D, MarlinConst {
y0 = y1;
}
final float lineLen = Helpers.linelen(curve[0], curve[1],
curve[_curveType-2],
curve[_curveType-1]);
if ((polyLen - lineLen) < ERR || recLevel == REC_LIMIT) {
final float lineLen = Helpers.linelen(curve[0], curve[1], x0, y0);
if ((polyLen - lineLen) < CURVE_LEN_ERR || recLevel == REC_LIMIT) {
return (polyLen + lineLen) / 2.0f;
}
return -1.0f;
@ -741,42 +923,191 @@ final class Dasher implements PathConsumer2D, MarlinConst {
public void curveTo(final float x1, final float y1,
final float x2, final float y2,
final float x3, final float y3)
{
final int outcode0 = this.cOutCode;
if (clipRect != null) {
final int outcode1 = Helpers.outcode(x1, y1, clipRect);
final int outcode2 = Helpers.outcode(x2, y2, clipRect);
final int outcode3 = Helpers.outcode(x3, y3, clipRect);
// Should clip
final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);
if (orCode != 0) {
final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;
// basic rejection criteria:
if (sideCode == 0) {
// ovelap clip:
if (subdivide) {
// avoid reentrance
subdivide = false;
// subdivide curve => callback with subdivided parts:
boolean ret = curveSplitter.splitCurve(cx0, cy0, x1, y1, x2, y2, x3, y3,
orCode, this);
// reentrance is done:
subdivide = true;
if (ret) {
return;
}
}
// already subdivided so render it
} else {
this.cOutCode = outcode3;
skipCurveTo(x1, y1, x2, y2, x3, y3);
return;
}
}
this.cOutCode = outcode3;
if (this.outside) {
this.outside = false;
// Adjust current index, phase & dash:
skipLen();
}
}
_curveTo(x1, y1, x2, y2, x3, y3);
}
private void _curveTo(final float x1, final float y1,
final float x2, final float y2,
final float x3, final float y3)
{
final float[] _curCurvepts = curCurvepts;
_curCurvepts[0] = x0; _curCurvepts[1] = y0;
_curCurvepts[2] = x1; _curCurvepts[3] = y1;
_curCurvepts[4] = x2; _curCurvepts[5] = y2;
_curCurvepts[6] = x3; _curCurvepts[7] = y3;
somethingTo(8);
// monotonize curve:
final CurveBasicMonotonizer monotonizer
= rdrCtx.monotonizer.curve(cx0, cy0, x1, y1, x2, y2, x3, y3);
final int nSplits = monotonizer.nbSplits;
final float[] mid = monotonizer.middle;
for (int i = 0, off = 0; i <= nSplits; i++, off += 6) {
// optimize arraycopy (8 values faster than 6 = type):
System.arraycopy(mid, off, _curCurvepts, 0, 8);
somethingTo(8);
}
}
private void skipCurveTo(final float x1, final float y1,
final float x2, final float y2,
final float x3, final float y3)
{
final float[] _curCurvepts = curCurvepts;
_curCurvepts[0] = cx0; _curCurvepts[1] = cy0;
_curCurvepts[2] = x1; _curCurvepts[3] = y1;
_curCurvepts[4] = x2; _curCurvepts[5] = y2;
_curCurvepts[6] = x3; _curCurvepts[7] = y3;
skipSomethingTo(8);
this.cx0 = x3;
this.cy0 = y3;
}
@Override
public void quadTo(final float x1, final float y1,
final float x2, final float y2)
{
final int outcode0 = this.cOutCode;
if (clipRect != null) {
final int outcode1 = Helpers.outcode(x1, y1, clipRect);
final int outcode2 = Helpers.outcode(x2, y2, clipRect);
// Should clip
final int orCode = (outcode0 | outcode1 | outcode2);
if (orCode != 0) {
final int sideCode = outcode0 & outcode1 & outcode2;
// basic rejection criteria:
if (sideCode == 0) {
// ovelap clip:
if (subdivide) {
// avoid reentrance
subdivide = false;
// subdivide curve => call lineTo() with subdivided curves:
boolean ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,
x2, y2, orCode, this);
// reentrance is done:
subdivide = true;
if (ret) {
return;
}
}
// already subdivided so render it
} else {
this.cOutCode = outcode2;
skipQuadTo(x1, y1, x2, y2);
return;
}
}
this.cOutCode = outcode2;
if (this.outside) {
this.outside = false;
// Adjust current index, phase & dash:
skipLen();
}
}
_quadTo(x1, y1, x2, y2);
}
private void _quadTo(final float x1, final float y1,
final float x2, final float y2)
{
final float[] _curCurvepts = curCurvepts;
_curCurvepts[0] = x0; _curCurvepts[1] = y0;
_curCurvepts[2] = x1; _curCurvepts[3] = y1;
_curCurvepts[4] = x2; _curCurvepts[5] = y2;
somethingTo(6);
// monotonize quad:
final CurveBasicMonotonizer monotonizer
= rdrCtx.monotonizer.quad(cx0, cy0, x1, y1, x2, y2);
final int nSplits = monotonizer.nbSplits;
final float[] mid = monotonizer.middle;
for (int i = 0, off = 0; i <= nSplits; i++, off += 4) {
// optimize arraycopy (8 values faster than 6 = type):
System.arraycopy(mid, off, _curCurvepts, 0, 8);
somethingTo(6);
}
}
private void skipQuadTo(final float x1, final float y1,
final float x2, final float y2)
{
final float[] _curCurvepts = curCurvepts;
_curCurvepts[0] = cx0; _curCurvepts[1] = cy0;
_curCurvepts[2] = x1; _curCurvepts[3] = y1;
_curCurvepts[4] = x2; _curCurvepts[5] = y2;
skipSomethingTo(6);
this.cx0 = x2;
this.cy0 = y2;
}
@Override
public void closePath() {
lineTo(sx, sy);
if (cx0 != sx0 || cy0 != sy0) {
lineTo(sx0, sy0);
}
if (firstSegidx != 0) {
if (!dashOn || needsMoveTo) {
out.moveTo(sx, sy);
out.moveTo(sx0, sy0);
}
emitFirstSegments();
}
moveTo(sx, sy);
moveTo(sx0, sy0);
}
@Override
public void pathDone() {
if (firstSegidx != 0) {
out.moveTo(sx, sy);
out.moveTo(sx0, sy0);
emitFirstSegments();
}
out.pathDone();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2018, 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
@ -99,7 +99,7 @@ final class DoubleArrayCache implements MarlinConst {
Reference(final DoubleArrayCache cache, final int initialSize) {
this.cache = cache;
this.clean = cache.clean;
this.initial = createArray(initialSize, clean);
this.initial = createArray(initialSize);
if (DO_STATS) {
cache.stats.totalInitial += initialSize;
}
@ -116,7 +116,7 @@ final class DoubleArrayCache implements MarlinConst {
logInfo(getLogPrefix(clean) + "DoubleArrayCache: "
+ "getArray[oversize]: length=\t" + length);
}
return createArray(length, clean);
return createArray(length);
}
double[] widenArray(final double[] array, final int usedSize,
@ -202,7 +202,7 @@ final class DoubleArrayCache implements MarlinConst {
if (DO_STATS) {
stats.createOp++;
}
return createArray(arraySize, clean);
return createArray(arraySize);
}
void putArray(final double[] array)
@ -229,12 +229,8 @@ final class DoubleArrayCache implements MarlinConst {
}
}
static double[] createArray(final int length, final boolean clean) {
if (clean) {
return new double[length];
}
// use JDK9 Unsafe.allocateUninitializedArray(class, length):
return (double[]) OffHeapArray.UNSAFE.allocateUninitializedArray(double.class, length);
static double[] createArray(final int length) {
return new double[length];
}
static void fill(final double[] array, final int fromIndex,

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2018, 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
@ -99,7 +99,7 @@ final class FloatArrayCache implements MarlinConst {
Reference(final FloatArrayCache cache, final int initialSize) {
this.cache = cache;
this.clean = cache.clean;
this.initial = createArray(initialSize, clean);
this.initial = createArray(initialSize);
if (DO_STATS) {
cache.stats.totalInitial += initialSize;
}
@ -116,7 +116,7 @@ final class FloatArrayCache implements MarlinConst {
logInfo(getLogPrefix(clean) + "FloatArrayCache: "
+ "getArray[oversize]: length=\t" + length);
}
return createArray(length, clean);
return createArray(length);
}
float[] widenArray(final float[] array, final int usedSize,
@ -202,7 +202,7 @@ final class FloatArrayCache implements MarlinConst {
if (DO_STATS) {
stats.createOp++;
}
return createArray(arraySize, clean);
return createArray(arraySize);
}
void putArray(final float[] array)
@ -229,12 +229,8 @@ final class FloatArrayCache implements MarlinConst {
}
}
static float[] createArray(final int length, final boolean clean) {
if (clean) {
return new float[length];
}
// use JDK9 Unsafe.allocateUninitializedArray(class, length):
return (float[]) OffHeapArray.UNSAFE.allocateUninitializedArray(float.class, length);
static float[] createArray(final int length) {
return new float[length];
}
static void fill(final float[] array, final int fromIndex,

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2018, 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
@ -25,7 +25,6 @@
package sun.java2d.marlin;
import static java.lang.Math.PI;
import java.util.Arrays;
import sun.awt.geom.PathConsumer2D;
import sun.java2d.marlin.stats.Histogram;
@ -47,13 +46,25 @@ final class Helpers implements MarlinConst {
return (d <= err && d >= -err);
}
static int quadraticRoots(final float a, final float b,
final float c, float[] zeroes, final int off)
static float evalCubic(final float a, final float b,
final float c, final float d,
final float t)
{
return t * (t * (t * a + b) + c) + d;
}
static float evalQuad(final float a, final float b,
final float c, final float t)
{
return t * (t * a + b) + c;
}
static int quadraticRoots(final float a, final float b, final float c,
final float[] zeroes, final int off)
{
int ret = off;
float t;
if (a != 0.0f) {
final float dis = b*b - 4*a*c;
final float dis = b*b - 4.0f * a * c;
if (dis > 0.0f) {
final float sqrtDis = (float) Math.sqrt(dis);
// depending on the sign of b we use a slightly different
@ -68,37 +79,38 @@ final class Helpers implements MarlinConst {
zeroes[ret++] = (2.0f * c) / (-b + sqrtDis);
}
} else if (dis == 0.0f) {
t = (-b) / (2.0f * a);
zeroes[ret++] = t;
}
} else {
if (b != 0.0f) {
t = (-c) / b;
zeroes[ret++] = t;
zeroes[ret++] = -b / (2.0f * a);
}
} else if (b != 0.0f) {
zeroes[ret++] = -c / b;
}
return ret - off;
}
// find the roots of g(t) = d*t^3 + a*t^2 + b*t + c in [A,B)
static int cubicRootsInAB(float d, float a, float b, float c,
float[] pts, final int off,
static int cubicRootsInAB(final float d0, float a0, float b0, float c0,
final float[] pts, final int off,
final float A, final float B)
{
if (d == 0.0f) {
int num = quadraticRoots(a, b, c, pts, off);
if (d0 == 0.0f) {
final int num = quadraticRoots(a0, b0, c0, pts, off);
return filterOutNotInAB(pts, off, num, A, B) - off;
}
// From Graphics Gems:
// http://tog.acm.org/resources/GraphicsGems/gems/Roots3And4.c
// https://github.com/erich666/GraphicsGems/blob/master/gems/Roots3And4.c
// (also from awt.geom.CubicCurve2D. But here we don't need as
// much accuracy and we don't want to create arrays so we use
// our own customized version).
// normal form: x^3 + ax^2 + bx + c = 0
a /= d;
b /= d;
c /= d;
// 2018.1: Need double precision if d is very small (flat curve) !
/*
* TODO: cleanup all that code after reading Roots3And4.c
*/
final double a = ((double)a0) / d0;
final double b = ((double)b0) / d0;
final double c = ((double)c0) / d0;
// substitute x = y - A/3 to eliminate quadratic term:
// x^3 +Px + Q = 0
@ -108,63 +120,45 @@ final class Helpers implements MarlinConst {
// p = P/3
// q = Q/2
// instead and use those values for simplicity of the code.
double sq_A = a * a;
double p = (1.0d/3.0d) * ((-1.0d/3.0d) * sq_A + b);
double q = (1.0d/2.0d) * ((2.0d/27.0d) * a * sq_A - (1.0d/3.0d) * a * b + c);
final double sub = (1.0d / 3.0d) * a;
final double sq_A = a * a;
final double p = (1.0d / 3.0d) * ((-1.0d / 3.0d) * sq_A + b);
final double q = (1.0d / 2.0d) * ((2.0d / 27.0d) * a * sq_A - sub * b + c);
// use Cardano's formula
double cb_p = p * p * p;
double D = q * q + cb_p;
final double cb_p = p * p * p;
final double D = q * q + cb_p;
int num;
if (D < 0.0d) {
// see: http://en.wikipedia.org/wiki/Cubic_function#Trigonometric_.28and_hyperbolic.29_method
final double phi = (1.0d/3.0d) * Math.acos(-q / Math.sqrt(-cb_p));
final double phi = (1.0d / 3.0d) * Math.acos(-q / Math.sqrt(-cb_p));
final double t = 2.0d * Math.sqrt(-p);
pts[ off+0 ] = (float) ( t * Math.cos(phi));
pts[ off+1 ] = (float) (-t * Math.cos(phi + (PI / 3.0d)));
pts[ off+2 ] = (float) (-t * Math.cos(phi - (PI / 3.0d)));
pts[off ] = (float) ( t * Math.cos(phi) - sub);
pts[off + 1] = (float) (-t * Math.cos(phi + (Math.PI / 3.0d)) - sub);
pts[off + 2] = (float) (-t * Math.cos(phi - (Math.PI / 3.0d)) - sub);
num = 3;
} else {
final double sqrt_D = Math.sqrt(D);
final double u = Math.cbrt(sqrt_D - q);
final double v = - Math.cbrt(sqrt_D + q);
pts[ off ] = (float) (u + v);
pts[off ] = (float) (u + v - sub);
num = 1;
if (within(D, 0.0d, 1e-8d)) {
pts[off+1] = -(pts[off] / 2.0f);
pts[off + 1] = (float)((-1.0d / 2.0d) * (u + v) - sub);
num = 2;
}
}
final float sub = (1.0f/3.0f) * a;
for (int i = 0; i < num; ++i) {
pts[ off+i ] -= sub;
}
return filterOutNotInAB(pts, off, num, A, B) - off;
}
static float evalCubic(final float a, final float b,
final float c, final float d,
final float t)
{
return t * (t * (t * a + b) + c) + d;
}
static float evalQuad(final float a, final float b,
final float c, final float t)
{
return t * (t * a + b) + c;
}
// returns the index 1 past the last valid element remaining after filtering
static int filterOutNotInAB(float[] nums, final int off, final int len,
static int filterOutNotInAB(final float[] nums, final int off, final int len,
final float a, final float b)
{
int ret = off;
@ -176,35 +170,190 @@ final class Helpers implements MarlinConst {
return ret;
}
static float linelen(float x1, float y1, float x2, float y2) {
final float dx = x2 - x1;
final float dy = y2 - y1;
return (float) Math.sqrt(dx*dx + dy*dy);
static float fastLineLen(final float x0, final float y0,
final float x1, final float y1)
{
final float dx = x1 - x0;
final float dy = y1 - y0;
// use manhattan norm:
return Math.abs(dx) + Math.abs(dy);
}
static void subdivide(float[] src, int srcoff, float[] left, int leftoff,
float[] right, int rightoff, int type)
static float linelen(final float x0, final float y0,
final float x1, final float y1)
{
final float dx = x1 - x0;
final float dy = y1 - y0;
return (float) Math.sqrt(dx * dx + dy * dy);
}
static float fastQuadLen(final float x0, final float y0,
final float x1, final float y1,
final float x2, final float y2)
{
final float dx1 = x1 - x0;
final float dx2 = x2 - x1;
final float dy1 = y1 - y0;
final float dy2 = y2 - y1;
// use manhattan norm:
return Math.abs(dx1) + Math.abs(dx2)
+ Math.abs(dy1) + Math.abs(dy2);
}
static float quadlen(final float x0, final float y0,
final float x1, final float y1,
final float x2, final float y2)
{
return (linelen(x0, y0, x1, y1)
+ linelen(x1, y1, x2, y2)
+ linelen(x0, y0, x2, y2)) / 2.0f;
}
static float fastCurvelen(final float x0, final float y0,
final float x1, final float y1,
final float x2, final float y2,
final float x3, final float y3)
{
final float dx1 = x1 - x0;
final float dx2 = x2 - x1;
final float dx3 = x3 - x2;
final float dy1 = y1 - y0;
final float dy2 = y2 - y1;
final float dy3 = y3 - y2;
// use manhattan norm:
return Math.abs(dx1) + Math.abs(dx2) + Math.abs(dx3)
+ Math.abs(dy1) + Math.abs(dy2) + Math.abs(dy3);
}
static float curvelen(final float x0, final float y0,
final float x1, final float y1,
final float x2, final float y2,
final float x3, final float y3)
{
return (linelen(x0, y0, x1, y1)
+ linelen(x1, y1, x2, y2)
+ linelen(x2, y2, x3, y3)
+ linelen(x0, y0, x3, y3)) / 2.0f;
}
// finds values of t where the curve in pts should be subdivided in order
// to get good offset curves a distance of w away from the middle curve.
// Stores the points in ts, and returns how many of them there were.
static int findSubdivPoints(final Curve c, final float[] pts,
final float[] ts, final int type,
final float w2)
{
final float x12 = pts[2] - pts[0];
final float y12 = pts[3] - pts[1];
// if the curve is already parallel to either axis we gain nothing
// from rotating it.
if ((y12 != 0.0f && x12 != 0.0f)) {
// we rotate it so that the first vector in the control polygon is
// parallel to the x-axis. This will ensure that rotated quarter
// circles won't be subdivided.
final float hypot = (float)Math.sqrt(x12 * x12 + y12 * y12);
final float cos = x12 / hypot;
final float sin = y12 / hypot;
final float x1 = cos * pts[0] + sin * pts[1];
final float y1 = cos * pts[1] - sin * pts[0];
final float x2 = cos * pts[2] + sin * pts[3];
final float y2 = cos * pts[3] - sin * pts[2];
final float x3 = cos * pts[4] + sin * pts[5];
final float y3 = cos * pts[5] - sin * pts[4];
switch(type) {
case 8:
final float x4 = cos * pts[6] + sin * pts[7];
final float y4 = cos * pts[7] - sin * pts[6];
c.set(x1, y1, x2, y2, x3, y3, x4, y4);
break;
case 6:
c.set(x1, y1, x2, y2, x3, y3);
break;
default:
}
} else {
c.set(pts, type);
}
int ret = 0;
// we subdivide at values of t such that the remaining rotated
// curves are monotonic in x and y.
ret += c.dxRoots(ts, ret);
ret += c.dyRoots(ts, ret);
// subdivide at inflection points.
if (type == 8) {
// quadratic curves can't have inflection points
ret += c.infPoints(ts, ret);
}
// now we must subdivide at points where one of the offset curves will have
// a cusp. This happens at ts where the radius of curvature is equal to w.
ret += c.rootsOfROCMinusW(ts, ret, w2, 0.0001f);
ret = filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f);
isort(ts, ret);
return ret;
}
// finds values of t where the curve in pts should be subdivided in order
// to get intersections with the given clip rectangle.
// Stores the points in ts, and returns how many of them there were.
static int findClipPoints(final Curve curve, final float[] pts,
final float[] ts, final int type,
final int outCodeOR,
final float[] clipRect)
{
curve.set(pts, type);
// clip rectangle (ymin, ymax, xmin, xmax)
int ret = 0;
if ((outCodeOR & OUTCODE_LEFT) != 0) {
ret += curve.xPoints(ts, ret, clipRect[2]);
}
if ((outCodeOR & OUTCODE_RIGHT) != 0) {
ret += curve.xPoints(ts, ret, clipRect[3]);
}
if ((outCodeOR & OUTCODE_TOP) != 0) {
ret += curve.yPoints(ts, ret, clipRect[0]);
}
if ((outCodeOR & OUTCODE_BOTTOM) != 0) {
ret += curve.yPoints(ts, ret, clipRect[1]);
}
isort(ts, ret);
return ret;
}
static void subdivide(final float[] src,
final float[] left, final float[] right,
final int type)
{
switch(type) {
case 6:
Helpers.subdivideQuad(src, srcoff, left, leftoff, right, rightoff);
return;
case 8:
Helpers.subdivideCubic(src, srcoff, left, leftoff, right, rightoff);
subdivideCubic(src, left, right);
return;
case 6:
subdivideQuad(src, left, right);
return;
default:
throw new InternalError("Unsupported curve type");
}
}
static void isort(float[] a, int off, int len) {
for (int i = off + 1, end = off + len; i < end; i++) {
float ai = a[i];
int j = i - 1;
for (; j >= off && a[j] > ai; j--) {
a[j+1] = a[j];
static void isort(final float[] a, final int len) {
for (int i = 1, j; i < len; i++) {
final float ai = a[i];
j = i - 1;
for (; j >= 0 && a[j] > ai; j--) {
a[j + 1] = a[j];
}
a[j+1] = ai;
a[j + 1] = ai;
}
}
@ -227,206 +376,216 @@ final class Helpers implements MarlinConst {
* equals (<code>leftoff</code> + 6), in order
* to avoid allocating extra storage for this common point.
* @param src the array holding the coordinates for the source curve
* @param srcoff the offset into the array of the beginning of the
* the 6 source coordinates
* @param left the array for storing the coordinates for the first
* half of the subdivided curve
* @param leftoff the offset into the array of the beginning of the
* the 6 left coordinates
* @param right the array for storing the coordinates for the second
* half of the subdivided curve
* @param rightoff the offset into the array of the beginning of the
* the 6 right coordinates
* @since 1.7
*/
static void subdivideCubic(float[] src, int srcoff,
float[] left, int leftoff,
float[] right, int rightoff)
static void subdivideCubic(final float[] src,
final float[] left,
final float[] right)
{
float x1 = src[srcoff + 0];
float y1 = src[srcoff + 1];
float ctrlx1 = src[srcoff + 2];
float ctrly1 = src[srcoff + 3];
float ctrlx2 = src[srcoff + 4];
float ctrly2 = src[srcoff + 5];
float x2 = src[srcoff + 6];
float y2 = src[srcoff + 7];
if (left != null) {
left[leftoff + 0] = x1;
left[leftoff + 1] = y1;
}
if (right != null) {
right[rightoff + 6] = x2;
right[rightoff + 7] = y2;
}
x1 = (x1 + ctrlx1) / 2.0f;
y1 = (y1 + ctrly1) / 2.0f;
x2 = (x2 + ctrlx2) / 2.0f;
y2 = (y2 + ctrly2) / 2.0f;
float centerx = (ctrlx1 + ctrlx2) / 2.0f;
float centery = (ctrly1 + ctrly2) / 2.0f;
ctrlx1 = (x1 + centerx) / 2.0f;
ctrly1 = (y1 + centery) / 2.0f;
ctrlx2 = (x2 + centerx) / 2.0f;
ctrly2 = (y2 + centery) / 2.0f;
centerx = (ctrlx1 + ctrlx2) / 2.0f;
centery = (ctrly1 + ctrly2) / 2.0f;
if (left != null) {
left[leftoff + 2] = x1;
left[leftoff + 3] = y1;
left[leftoff + 4] = ctrlx1;
left[leftoff + 5] = ctrly1;
left[leftoff + 6] = centerx;
left[leftoff + 7] = centery;
}
if (right != null) {
right[rightoff + 0] = centerx;
right[rightoff + 1] = centery;
right[rightoff + 2] = ctrlx2;
right[rightoff + 3] = ctrly2;
right[rightoff + 4] = x2;
right[rightoff + 5] = y2;
}
float x1 = src[0];
float y1 = src[1];
float cx1 = src[2];
float cy1 = src[3];
float cx2 = src[4];
float cy2 = src[5];
float x2 = src[6];
float y2 = src[7];
left[0] = x1;
left[1] = y1;
right[6] = x2;
right[7] = y2;
x1 = (x1 + cx1) / 2.0f;
y1 = (y1 + cy1) / 2.0f;
x2 = (x2 + cx2) / 2.0f;
y2 = (y2 + cy2) / 2.0f;
float cx = (cx1 + cx2) / 2.0f;
float cy = (cy1 + cy2) / 2.0f;
cx1 = (x1 + cx) / 2.0f;
cy1 = (y1 + cy) / 2.0f;
cx2 = (x2 + cx) / 2.0f;
cy2 = (y2 + cy) / 2.0f;
cx = (cx1 + cx2) / 2.0f;
cy = (cy1 + cy2) / 2.0f;
left[2] = x1;
left[3] = y1;
left[4] = cx1;
left[5] = cy1;
left[6] = cx;
left[7] = cy;
right[0] = cx;
right[1] = cy;
right[2] = cx2;
right[3] = cy2;
right[4] = x2;
right[5] = y2;
}
static void subdivideCubicAt(float t, float[] src, int srcoff,
float[] left, int leftoff,
float[] right, int rightoff)
static void subdivideCubicAt(final float t,
final float[] src, final int offS,
final float[] pts, final int offL, final int offR)
{
float x1 = src[srcoff + 0];
float y1 = src[srcoff + 1];
float ctrlx1 = src[srcoff + 2];
float ctrly1 = src[srcoff + 3];
float ctrlx2 = src[srcoff + 4];
float ctrly2 = src[srcoff + 5];
float x2 = src[srcoff + 6];
float y2 = src[srcoff + 7];
if (left != null) {
left[leftoff + 0] = x1;
left[leftoff + 1] = y1;
}
if (right != null) {
right[rightoff + 6] = x2;
right[rightoff + 7] = y2;
}
x1 = x1 + t * (ctrlx1 - x1);
y1 = y1 + t * (ctrly1 - y1);
x2 = ctrlx2 + t * (x2 - ctrlx2);
y2 = ctrly2 + t * (y2 - ctrly2);
float centerx = ctrlx1 + t * (ctrlx2 - ctrlx1);
float centery = ctrly1 + t * (ctrly2 - ctrly1);
ctrlx1 = x1 + t * (centerx - x1);
ctrly1 = y1 + t * (centery - y1);
ctrlx2 = centerx + t * (x2 - centerx);
ctrly2 = centery + t * (y2 - centery);
centerx = ctrlx1 + t * (ctrlx2 - ctrlx1);
centery = ctrly1 + t * (ctrly2 - ctrly1);
if (left != null) {
left[leftoff + 2] = x1;
left[leftoff + 3] = y1;
left[leftoff + 4] = ctrlx1;
left[leftoff + 5] = ctrly1;
left[leftoff + 6] = centerx;
left[leftoff + 7] = centery;
}
if (right != null) {
right[rightoff + 0] = centerx;
right[rightoff + 1] = centery;
right[rightoff + 2] = ctrlx2;
right[rightoff + 3] = ctrly2;
right[rightoff + 4] = x2;
right[rightoff + 5] = y2;
}
float x1 = src[offS ];
float y1 = src[offS + 1];
float cx1 = src[offS + 2];
float cy1 = src[offS + 3];
float cx2 = src[offS + 4];
float cy2 = src[offS + 5];
float x2 = src[offS + 6];
float y2 = src[offS + 7];
pts[offL ] = x1;
pts[offL + 1] = y1;
pts[offR + 6] = x2;
pts[offR + 7] = y2;
x1 = x1 + t * (cx1 - x1);
y1 = y1 + t * (cy1 - y1);
x2 = cx2 + t * (x2 - cx2);
y2 = cy2 + t * (y2 - cy2);
float cx = cx1 + t * (cx2 - cx1);
float cy = cy1 + t * (cy2 - cy1);
cx1 = x1 + t * (cx - x1);
cy1 = y1 + t * (cy - y1);
cx2 = cx + t * (x2 - cx);
cy2 = cy + t * (y2 - cy);
cx = cx1 + t * (cx2 - cx1);
cy = cy1 + t * (cy2 - cy1);
pts[offL + 2] = x1;
pts[offL + 3] = y1;
pts[offL + 4] = cx1;
pts[offL + 5] = cy1;
pts[offL + 6] = cx;
pts[offL + 7] = cy;
pts[offR ] = cx;
pts[offR + 1] = cy;
pts[offR + 2] = cx2;
pts[offR + 3] = cy2;
pts[offR + 4] = x2;
pts[offR + 5] = y2;
}
static void subdivideQuad(float[] src, int srcoff,
float[] left, int leftoff,
float[] right, int rightoff)
static void subdivideQuad(final float[] src,
final float[] left,
final float[] right)
{
float x1 = src[srcoff + 0];
float y1 = src[srcoff + 1];
float ctrlx = src[srcoff + 2];
float ctrly = src[srcoff + 3];
float x2 = src[srcoff + 4];
float y2 = src[srcoff + 5];
if (left != null) {
left[leftoff + 0] = x1;
left[leftoff + 1] = y1;
}
if (right != null) {
right[rightoff + 4] = x2;
right[rightoff + 5] = y2;
}
x1 = (x1 + ctrlx) / 2.0f;
y1 = (y1 + ctrly) / 2.0f;
x2 = (x2 + ctrlx) / 2.0f;
y2 = (y2 + ctrly) / 2.0f;
ctrlx = (x1 + x2) / 2.0f;
ctrly = (y1 + y2) / 2.0f;
if (left != null) {
left[leftoff + 2] = x1;
left[leftoff + 3] = y1;
left[leftoff + 4] = ctrlx;
left[leftoff + 5] = ctrly;
}
if (right != null) {
right[rightoff + 0] = ctrlx;
right[rightoff + 1] = ctrly;
right[rightoff + 2] = x2;
right[rightoff + 3] = y2;
}
float x1 = src[0];
float y1 = src[1];
float cx = src[2];
float cy = src[3];
float x2 = src[4];
float y2 = src[5];
left[0] = x1;
left[1] = y1;
right[4] = x2;
right[5] = y2;
x1 = (x1 + cx) / 2.0f;
y1 = (y1 + cy) / 2.0f;
x2 = (x2 + cx) / 2.0f;
y2 = (y2 + cy) / 2.0f;
cx = (x1 + x2) / 2.0f;
cy = (y1 + y2) / 2.0f;
left[2] = x1;
left[3] = y1;
left[4] = cx;
left[5] = cy;
right[0] = cx;
right[1] = cy;
right[2] = x2;
right[3] = y2;
}
static void subdivideQuadAt(float t, float[] src, int srcoff,
float[] left, int leftoff,
float[] right, int rightoff)
static void subdivideQuadAt(final float t,
final float[] src, final int offS,
final float[] pts, final int offL, final int offR)
{
float x1 = src[srcoff + 0];
float y1 = src[srcoff + 1];
float ctrlx = src[srcoff + 2];
float ctrly = src[srcoff + 3];
float x2 = src[srcoff + 4];
float y2 = src[srcoff + 5];
if (left != null) {
left[leftoff + 0] = x1;
left[leftoff + 1] = y1;
}
if (right != null) {
right[rightoff + 4] = x2;
right[rightoff + 5] = y2;
}
x1 = x1 + t * (ctrlx - x1);
y1 = y1 + t * (ctrly - y1);
x2 = ctrlx + t * (x2 - ctrlx);
y2 = ctrly + t * (y2 - ctrly);
ctrlx = x1 + t * (x2 - x1);
ctrly = y1 + t * (y2 - y1);
if (left != null) {
left[leftoff + 2] = x1;
left[leftoff + 3] = y1;
left[leftoff + 4] = ctrlx;
left[leftoff + 5] = ctrly;
}
if (right != null) {
right[rightoff + 0] = ctrlx;
right[rightoff + 1] = ctrly;
right[rightoff + 2] = x2;
right[rightoff + 3] = y2;
}
float x1 = src[offS ];
float y1 = src[offS + 1];
float cx = src[offS + 2];
float cy = src[offS + 3];
float x2 = src[offS + 4];
float y2 = src[offS + 5];
pts[offL ] = x1;
pts[offL + 1] = y1;
pts[offR + 4] = x2;
pts[offR + 5] = y2;
x1 = x1 + t * (cx - x1);
y1 = y1 + t * (cy - y1);
x2 = cx + t * (x2 - cx);
y2 = cy + t * (y2 - cy);
cx = x1 + t * (x2 - x1);
cy = y1 + t * (y2 - y1);
pts[offL + 2] = x1;
pts[offL + 3] = y1;
pts[offL + 4] = cx;
pts[offL + 5] = cy;
pts[offR ] = cx;
pts[offR + 1] = cy;
pts[offR + 2] = x2;
pts[offR + 3] = y2;
}
static void subdivideAt(float t, float[] src, int srcoff,
float[] left, int leftoff,
float[] right, int rightoff, int size)
static void subdivideLineAt(final float t,
final float[] src, final int offS,
final float[] pts, final int offL, final int offR)
{
switch(size) {
case 8:
subdivideCubicAt(t, src, srcoff, left, leftoff, right, rightoff);
return;
case 6:
subdivideQuadAt(t, src, srcoff, left, leftoff, right, rightoff);
return;
float x1 = src[offS ];
float y1 = src[offS + 1];
float x2 = src[offS + 2];
float y2 = src[offS + 3];
pts[offL ] = x1;
pts[offL + 1] = y1;
pts[offR + 2] = x2;
pts[offR + 3] = y2;
x1 = x1 + t * (x2 - x1);
y1 = y1 + t * (y2 - y1);
pts[offL + 2] = x1;
pts[offL + 3] = y1;
pts[offR ] = x1;
pts[offR + 1] = y1;
}
static void subdivideAt(final float t,
final float[] src, final int offS,
final float[] pts, final int offL, final int type)
{
// if instead of switch (perf + most probable cases first)
if (type == 8) {
subdivideCubicAt(t, src, offS, pts, offL, offL + type);
} else if (type == 4) {
subdivideLineAt(t, src, offS, pts, offL, offL + type);
} else {
subdivideQuadAt(t, src, offS, pts, offL, offL + type);
}
}
@ -614,12 +773,12 @@ final class Helpers implements MarlinConst {
e += 2;
continue;
case TYPE_QUADTO:
io.quadTo(_curves[e+0], _curves[e+1],
io.quadTo(_curves[e], _curves[e+1],
_curves[e+2], _curves[e+3]);
e += 4;
continue;
case TYPE_CUBICTO:
io.curveTo(_curves[e+0], _curves[e+1],
io.curveTo(_curves[e], _curves[e+1],
_curves[e+2], _curves[e+3],
_curves[e+4], _curves[e+5]);
e += 6;
@ -657,12 +816,12 @@ final class Helpers implements MarlinConst {
continue;
case TYPE_QUADTO:
e -= 4;
io.quadTo(_curves[e+0], _curves[e+1],
io.quadTo(_curves[e], _curves[e+1],
_curves[e+2], _curves[e+3]);
continue;
case TYPE_CUBICTO:
e -= 6;
io.curveTo(_curves[e+0], _curves[e+1],
io.curveTo(_curves[e], _curves[e+1],
_curves[e+2], _curves[e+3],
_curves[e+4], _curves[e+5]);
continue;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2018, 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
@ -99,7 +99,7 @@ final class IntArrayCache implements MarlinConst {
Reference(final IntArrayCache cache, final int initialSize) {
this.cache = cache;
this.clean = cache.clean;
this.initial = createArray(initialSize, clean);
this.initial = createArray(initialSize);
if (DO_STATS) {
cache.stats.totalInitial += initialSize;
}
@ -116,7 +116,7 @@ final class IntArrayCache implements MarlinConst {
logInfo(getLogPrefix(clean) + "IntArrayCache: "
+ "getArray[oversize]: length=\t" + length);
}
return createArray(length, clean);
return createArray(length);
}
int[] widenArray(final int[] array, final int usedSize,
@ -202,7 +202,7 @@ final class IntArrayCache implements MarlinConst {
if (DO_STATS) {
stats.createOp++;
}
return createArray(arraySize, clean);
return createArray(arraySize);
}
void putArray(final int[] array)
@ -229,12 +229,8 @@ final class IntArrayCache implements MarlinConst {
}
}
static int[] createArray(final int length, final boolean clean) {
if (clean) {
return new int[length];
}
// use JDK9 Unsafe.allocateUninitializedArray(class, length):
return (int[]) OffHeapArray.UNSAFE.allocateUninitializedArray(int.class, length);
static int[] createArray(final int length) {
return new int[length];
}
static void fill(final int[] array, final int fromIndex,

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2018, 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
@ -43,9 +43,9 @@ public final class MarlinCache implements MarlinConst {
// values are stored as int [x|alpha] where alpha is 8 bits
static final int RLE_MAX_WIDTH = 1 << (24 - 1);
// 2048 (pixelSize) alpha values (width) x 32 rows (tile) = 64K bytes
// 4096 (pixels) alpha values (width) x 64 rows / 4 (tile) = 64K bytes
// x1 instead of 4 bytes (RLE) ie 1/4 capacity or average good RLE compression
static final long INITIAL_CHUNK_ARRAY = TILE_H * INITIAL_PIXEL_DIM; // 64K
static final long INITIAL_CHUNK_ARRAY = TILE_H * INITIAL_PIXEL_WIDTH >> 2; // 64K
// The alpha map used by this object (taken out of our map cache) to convert
// pixel coverage counts gotten from MarlinCache (which are in the range
@ -292,11 +292,11 @@ public final class MarlinCache implements MarlinConst {
// ensure values are in [0; MAX_AA_ALPHA] range
if (DO_AA_RANGE_CHECK) {
if (val < 0) {
System.out.println("Invalid coverage = " + val);
MarlinUtils.logInfo("Invalid coverage = " + val);
val = 0;
}
if (val > MAX_AA_ALPHA) {
System.out.println("Invalid coverage = " + val);
MarlinUtils.logInfo("Invalid coverage = " + val);
val = MAX_AA_ALPHA;
}
}
@ -460,11 +460,11 @@ public final class MarlinCache implements MarlinConst {
// ensure values are in [0; MAX_AA_ALPHA] range
if (DO_AA_RANGE_CHECK) {
if (val < 0) {
System.out.println("Invalid coverage = " + val);
MarlinUtils.logInfo("Invalid coverage = " + val);
val = 0;
}
if (val > MAX_AA_ALPHA) {
System.out.println("Invalid coverage = " + val);
MarlinUtils.logInfo("Invalid coverage = " + val);
val = MAX_AA_ALPHA;
}
}
@ -630,8 +630,6 @@ public final class MarlinCache implements MarlinConst {
final int halfmaxalpha = maxalpha >> 2;
for (int i = 0; i <= maxalpha; i++) {
alMap[i] = (byte) ((i * 255 + halfmaxalpha) / maxalpha);
// System.out.println("alphaMap[" + i + "] = "
// + Byte.toUnsignedInt(alMap[i]));
}
return alMap;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2018, 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
@ -74,23 +74,34 @@ interface MarlinConst {
// do clean dirty array
static final boolean DO_CLEAN_DIRTY = false;
// flag to use line simplifier
// flag to use collinear simplifier
static final boolean USE_SIMPLIFIER = MarlinProperties.isUseSimplifier();
// flag to use path simplifier
static final boolean USE_PATH_SIMPLIFIER = MarlinProperties.isUsePathSimplifier();
static final boolean DO_CLIP_SUBDIVIDER = MarlinProperties.isDoClipSubdivider();
// flag to enable logs related bounds checks
static final boolean DO_LOG_BOUNDS = ENABLE_LOGS && false;
// flag to enable float precision correction
static final boolean DO_FIX_FLOAT_PREC = true;
// Initial Array sizing (initial context capacity) ~ 450K
// 2048 pixel (width x height) for initial capacity
static final int INITIAL_PIXEL_DIM
= MarlinProperties.getInitialImageSize();
// 4096 pixels (width) for initial capacity
static final int INITIAL_PIXEL_WIDTH
= MarlinProperties.getInitialPixelWidth();
// 2176 pixels (height) for initial capacity
static final int INITIAL_PIXEL_HEIGHT
= MarlinProperties.getInitialPixelHeight();
// typical array sizes: only odd numbers allowed below
static final int INITIAL_ARRAY = 256;
// alpha row dimension
static final int INITIAL_AA_ARRAY = INITIAL_PIXEL_DIM;
static final int INITIAL_AA_ARRAY = INITIAL_PIXEL_WIDTH;
// 4096 edges for initial capacity
static final int INITIAL_EDGES_COUNT = MarlinProperties.getInitialEdges();
@ -109,16 +120,17 @@ interface MarlinConst {
public static final int SUBPIXEL_LG_POSITIONS_Y
= MarlinProperties.getSubPixel_Log2_Y();
public static final int MIN_SUBPIXEL_LG_POSITIONS
= Math.min(SUBPIXEL_LG_POSITIONS_X, SUBPIXEL_LG_POSITIONS_Y);
// number of subpixels
public static final int SUBPIXEL_POSITIONS_X = 1 << (SUBPIXEL_LG_POSITIONS_X);
public static final int SUBPIXEL_POSITIONS_Y = 1 << (SUBPIXEL_LG_POSITIONS_Y);
public static final float NORM_SUBPIXELS
= (float) Math.sqrt(( SUBPIXEL_POSITIONS_X * SUBPIXEL_POSITIONS_X
+ SUBPIXEL_POSITIONS_Y * SUBPIXEL_POSITIONS_Y) / 2.0d);
public static final float MIN_SUBPIXELS = 1 << MIN_SUBPIXEL_LG_POSITIONS;
public static final int MAX_AA_ALPHA
= SUBPIXEL_POSITIONS_X * SUBPIXEL_POSITIONS_Y;
= (SUBPIXEL_POSITIONS_X * SUBPIXEL_POSITIONS_Y);
public static final int TILE_H_LG = MarlinProperties.getTileSize_Log2();
public static final int TILE_H = 1 << TILE_H_LG; // 32 by default

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2018, 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
@ -54,29 +54,41 @@ public final class MarlinProperties {
}
/**
* Return the initial pixel size used to define initial arrays
* (tile AA chunk, alpha line, buckets)
* Return the initial pixel width used to define initial arrays
* (tile AA chunk, alpha line)
*
* @return 64 < initial pixel size < 32768 (2048 by default)
* @return 64 < initial pixel size < 32768 (4096 by default)
*/
public static int getInitialImageSize() {
public static int getInitialPixelWidth() {
return align(
getInteger("sun.java2d.renderer.pixelsize", 2048, 64, 32 * 1024),
getInteger("sun.java2d.renderer.pixelWidth", 4096, 64, 32 * 1024),
64);
}
/**
* Return the log(2) corresponding to subpixel on x-axis (
* Return the initial pixel height used to define initial arrays
* (buckets)
*
* @return 0 (1 subpixels) < initial pixel size < 8 (256 subpixels)
* (3 by default ie 8 subpixels)
* @return 64 < initial pixel size < 32768 (2176 by default)
*/
public static int getSubPixel_Log2_X() {
return getInteger("sun.java2d.renderer.subPixel_log2_X", 3, 0, 8);
public static int getInitialPixelHeight() {
return align(
getInteger("sun.java2d.renderer.pixelHeight", 2176, 64, 32 * 1024),
64);
}
/**
* Return the log(2) corresponding to subpixel on y-axis (
* Return the log(2) corresponding to subpixel on x-axis
*
* @return 0 (1 subpixels) < initial pixel size < 8 (256 subpixels)
* (8 by default ie 256 subpixels)
*/
public static int getSubPixel_Log2_X() {
return getInteger("sun.java2d.renderer.subPixel_log2_X", 8, 0, 8);
}
/**
* Return the log(2) corresponding to subpixel on y-axis
*
* @return 0 (1 subpixels) < initial pixel size < 8 (256 subpixels)
* (3 by default ie 8 subpixels)
@ -88,7 +100,7 @@ public final class MarlinProperties {
/**
* Return the log(2) corresponding to the square tile size in pixels
*
* @return 3 (8x8 pixels) < tile size < 8 (256x256 pixels)
* @return 3 (8x8 pixels) < tile size < 10 (1024x1024 pixels)
* (5 by default ie 32x32 pixels)
*/
public static int getTileSize_Log2() {
@ -98,12 +110,11 @@ public final class MarlinProperties {
/**
* Return the log(2) corresponding to the tile width in pixels
*
* @return 3 (8 pixels) < tile with < 8 (256 pixels)
* (by default is given by the square tile size)
* @return 3 (8 pixels) < tile width < 10 (1024 pixels)
* (5 by default ie 32x32 pixels)
*/
public static int getTileWidth_Log2() {
final int tileSize = getTileSize_Log2();
return getInteger("sun.java2d.renderer.tileWidth_log2", tileSize, 3, 10);
return getInteger("sun.java2d.renderer.tileWidth_log2", 5, 3, 10);
}
/**
@ -145,6 +156,18 @@ public final class MarlinProperties {
return getBoolean("sun.java2d.renderer.useSimplifier", "false");
}
public static boolean isUsePathSimplifier() {
return getBoolean("sun.java2d.renderer.usePathSimplifier", "false");
}
public static float getPathSimplifierPixelTolerance() {
// default: MIN_PEN_SIZE or less ?
return getFloat("sun.java2d.renderer.pathSimplifier.pixTol",
(1.0f / MarlinConst.MIN_SUBPIXELS),
1e-3f,
10.0f);
}
public static boolean isDoClip() {
return getBoolean("sun.java2d.renderer.clip", "true");
}
@ -157,6 +180,14 @@ public final class MarlinProperties {
return getBoolean("sun.java2d.renderer.clip.runtime", "true");
}
public static boolean isDoClipSubdivider() {
return getBoolean("sun.java2d.renderer.clip.subdivider", "true");
}
public static float getSubdividerMinLength() {
return getFloat("sun.java2d.renderer.clip.subdivider.minLength", 100.0f, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY);
}
// debugging parameters
public static boolean isDoStats() {
@ -191,16 +222,20 @@ public final class MarlinProperties {
// quality settings
public static float getCurveLengthError() {
return getFloat("sun.java2d.renderer.curve_len_err", 0.01f, 1e-6f, 1.0f);
}
public static float getCubicDecD2() {
return getFloat("sun.java2d.renderer.cubic_dec_d2", 1.0f, 0.01f, 4.0f);
return getFloat("sun.java2d.renderer.cubic_dec_d2", 1.0f, 1e-5f, 4.0f);
}
public static float getCubicIncD1() {
return getFloat("sun.java2d.renderer.cubic_inc_d1", 0.4f, 0.01f, 2.0f);
return getFloat("sun.java2d.renderer.cubic_inc_d1", 0.2f, 1e-6f, 1.0f);
}
public static float getQuadDecD2() {
return getFloat("sun.java2d.renderer.quad_dec_d2", 0.5f, 0.01f, 4.0f);
return getFloat("sun.java2d.renderer.quad_dec_d2", 0.5f, 1e-5f, 4.0f);
}
// system property utilities

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2018, 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
@ -47,7 +47,21 @@ import sun.security.action.GetPropertyAction;
public final class MarlinRenderingEngine extends RenderingEngine
implements MarlinConst
{
private static enum NormMode {
// slightly slower ~2% if enabled stroker clipping (lines) but skipping cap / join handling is few percents faster in specific cases
static final boolean DISABLE_2ND_STROKER_CLIPPING = true;
static final boolean DO_TRACE_PATH = false;
static final boolean DO_CLIP = MarlinProperties.isDoClip();
static final boolean DO_CLIP_FILL = true;
static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag();
private static final float MIN_PEN_SIZE = 1.0f / MIN_SUBPIXELS;
static final float UPPER_BND = Float.MAX_VALUE / 2.0f;
static final float LOWER_BND = -UPPER_BND;
private enum NormMode {
ON_WITH_AA {
@Override
PathIterator getNormalizingPathIterator(final RendererContext rdrCtx,
@ -80,18 +94,6 @@ public final class MarlinRenderingEngine extends RenderingEngine
PathIterator src);
}
private static final float MIN_PEN_SIZE = 1.0f / NORM_SUBPIXELS;
static final float UPPER_BND = Float.MAX_VALUE / 2.0f;
static final float LOWER_BND = -UPPER_BND;
static final boolean DO_CLIP = MarlinProperties.isDoClip();
static final boolean DO_CLIP_FILL = true;
static final boolean DO_TRACE_PATH = false;
static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag();
/**
* Public constructor
*/
@ -419,14 +421,27 @@ public final class MarlinRenderingEngine extends RenderingEngine
pc2d = transformerPC2D.deltaTransformConsumer(pc2d, strokerat);
// stroker will adjust the clip rectangle (width / miter limit):
pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit, scale);
pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit, scale,
(dashes == null));
// Curve Monotizer:
rdrCtx.monotonizer.init(width);
if (dashes != null) {
if (!recycleDashes) {
dashLen = dashes.length;
}
if (DO_TRACE_PATH) {
pc2d = transformerPC2D.traceDasher(pc2d);
}
pc2d = rdrCtx.dasher.init(pc2d, dashes, dashLen, dashphase,
recycleDashes);
if (DISABLE_2ND_STROKER_CLIPPING) {
// disable stoker clipping
rdrCtx.stroker.disableClipping();
}
} else if (rdrCtx.doClip && (caps != Stroker.CAP_BUTT)) {
if (DO_TRACE_PATH) {
pc2d = transformerPC2D.traceClosedPathDetector(pc2d);
@ -625,6 +640,12 @@ public final class MarlinRenderingEngine extends RenderingEngine
private static void pathTo(final RendererContext rdrCtx, final PathIterator pi,
PathConsumer2D pc2d)
{
if (USE_PATH_SIMPLIFIER) {
// Use path simplifier at the first step
// to remove useless points
pc2d = rdrCtx.pathSimplifier.init(pc2d);
}
// mark context as DIRTY:
rdrCtx.dirty = true;
@ -849,8 +870,6 @@ public final class MarlinRenderingEngine extends RenderingEngine
// trace Input:
pc2d = rdrCtx.transformerPC2D.traceInput(pc2d);
}
// TODO: subdivide quad/cubic curves into monotonic curves ?
pathTo(rdrCtx, pi, pc2d);
} else {
@ -1070,8 +1089,10 @@ public final class MarlinRenderingEngine extends RenderingEngine
logInfo("sun.java2d.renderer.edges = "
+ MarlinConst.INITIAL_EDGES_COUNT);
logInfo("sun.java2d.renderer.pixelsize = "
+ MarlinConst.INITIAL_PIXEL_DIM);
logInfo("sun.java2d.renderer.pixelWidth = "
+ MarlinConst.INITIAL_PIXEL_WIDTH);
logInfo("sun.java2d.renderer.pixelHeight = "
+ MarlinConst.INITIAL_PIXEL_HEIGHT);
logInfo("sun.java2d.renderer.subPixel_log2_X = "
+ MarlinConst.SUBPIXEL_LG_POSITIONS_X);
@ -1101,12 +1122,21 @@ public final class MarlinRenderingEngine extends RenderingEngine
// optimisation parameters
logInfo("sun.java2d.renderer.useSimplifier = "
+ MarlinConst.USE_SIMPLIFIER);
logInfo("sun.java2d.renderer.usePathSimplifier= "
+ MarlinConst.USE_PATH_SIMPLIFIER);
logInfo("sun.java2d.renderer.pathSimplifier.pixTol = "
+ MarlinProperties.getPathSimplifierPixelTolerance());
logInfo("sun.java2d.renderer.clip = "
+ MarlinProperties.isDoClip());
logInfo("sun.java2d.renderer.clip.runtime.enable = "
+ MarlinProperties.isDoClipRuntimeFlag());
logInfo("sun.java2d.renderer.clip.subdivider = "
+ MarlinProperties.isDoClipSubdivider());
logInfo("sun.java2d.renderer.clip.subdivider.minLength = "
+ MarlinProperties.getSubdividerMinLength());
// debugging parameters
logInfo("sun.java2d.renderer.doStats = "
+ MarlinConst.DO_STATS);
@ -1124,6 +1154,8 @@ public final class MarlinRenderingEngine extends RenderingEngine
+ MarlinConst.LOG_UNSAFE_MALLOC);
// quality settings
logInfo("sun.java2d.renderer.curve_len_err = "
+ MarlinProperties.getCurveLengthError());
logInfo("sun.java2d.renderer.cubic_dec_d2 = "
+ MarlinProperties.getCubicDecD2());
logInfo("sun.java2d.renderer.cubic_inc_d1 = "

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2018, 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
@ -31,6 +31,8 @@ import jdk.internal.misc.Unsafe;
final class MarlinTileGenerator implements AATileGenerator, MarlinConst {
private static final boolean DISABLE_BLEND = false;
private static final int MAX_TILE_ALPHA_SUM = TILE_W * TILE_H * MAX_AA_ALPHA;
private static final int TH_AA_ALPHA_FILL_EMPTY = ((MAX_AA_ALPHA + 1) / 3); // 33%
@ -43,10 +45,10 @@ final class MarlinTileGenerator implements AATileGenerator, MarlinConst {
throw new IllegalStateException("Invalid MAX_TILE_ALPHA_SUM: " + MAX_TILE_ALPHA_SUM);
}
if (DO_TRACE) {
System.out.println("MAX_AA_ALPHA : " + MAX_AA_ALPHA);
System.out.println("TH_AA_ALPHA_FILL_EMPTY : " + TH_AA_ALPHA_FILL_EMPTY);
System.out.println("TH_AA_ALPHA_FILL_FULL : " + TH_AA_ALPHA_FILL_FULL);
System.out.println("FILL_TILE_W : " + FILL_TILE_W);
MarlinUtils.logInfo("MAX_AA_ALPHA : " + MAX_AA_ALPHA);
MarlinUtils.logInfo("TH_AA_ALPHA_FILL_EMPTY : " + TH_AA_ALPHA_FILL_EMPTY);
MarlinUtils.logInfo("TH_AA_ALPHA_FILL_FULL : " + TH_AA_ALPHA_FILL_FULL);
MarlinUtils.logInfo("FILL_TILE_W : " + FILL_TILE_W);
}
}
@ -141,6 +143,10 @@ final class MarlinTileGenerator implements AATileGenerator, MarlinConst {
*/
@Override
public int getTypicalAlpha() {
if (DISABLE_BLEND) {
// always return empty tiles to disable blending operations
return 0x00;
}
int al = cache.alphaSumInTile(x);
// Note: if we have a filled rectangle that doesn't end on a tile
// border, we could still return 0xff, even though al!=maxTileAlphaSum

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2018, 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
@ -60,4 +60,22 @@ public final class MarlinUtils {
th.printStackTrace(System.err);
}
}
// From sun.awt.util.ThreadGroupUtils
/**
* Returns a root thread group.
* Should be called with {@link sun.security.util.SecurityConstants#MODIFY_THREADGROUP_PERMISSION}
*
* @return a root {@code ThreadGroup}
*/
public static ThreadGroup getRootThreadGroup() {
ThreadGroup currentTG = Thread.currentThread().getThreadGroup();
ThreadGroup parentTG = currentTG.getParent();
while (parentTG != null) {
currentTG = parentTG;
parentTG = currentTG.getParent();
}
return currentTG;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2009, 2018, 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
@ -61,7 +61,6 @@ final class MergeSort {
// Merge sorted parts (auxX/auxY) into x/y arrays
if ((insertionSortIndex == 0)
|| (auxX[insertionSortIndex - 1] <= auxX[insertionSortIndex])) {
// System.out.println("mergeSortNoCopy: ordered");
// 34 occurences
// no initial left part or both sublists (auxX, auxY) are sorted:
// copy back data into (x, y):
@ -135,7 +134,6 @@ final class MergeSort {
// If arrays are inverted ie all(A) > all(B) do swap A and B to dst
if (srcX[high - 1] <= srcX[low]) {
// System.out.println("mergeSort: inverse ordered");
// 1561 occurences
final int left = mid - low;
final int right = high - mid;
@ -151,7 +149,6 @@ final class MergeSort {
// If arrays are already sorted, just copy from src to dest. This is an
// optimization that results in faster sorts for nearly ordered lists.
if (srcX[mid - 1] <= srcX[mid]) {
// System.out.println("mergeSort: ordered");
// 14 occurences
System.arraycopy(srcX, low, dstX, low, length);
System.arraycopy(srcY, low, dstY, low, length);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2018, 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
@ -31,7 +31,6 @@ import jdk.internal.ref.CleanerFactory;
/**
*
* @author bourgesl
*/
final class OffHeapArray {

View File

@ -0,0 +1,138 @@
/*
* Copyright (c) 2017, 2018, 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.marlin;
import sun.awt.geom.PathConsumer2D;
final class PathSimplifier implements PathConsumer2D {
// distance threshold in pixels (device)
private static final float PIX_THRESHOLD = MarlinProperties.getPathSimplifierPixelTolerance();
private static final float SQUARE_TOLERANCE = PIX_THRESHOLD * PIX_THRESHOLD;
// members:
private PathConsumer2D delegate;
private float cx, cy;
PathSimplifier() {
}
PathSimplifier init(final PathConsumer2D delegate) {
this.delegate = delegate;
return this; // fluent API
}
@Override
public void pathDone() {
delegate.pathDone();
}
@Override
public void closePath() {
delegate.closePath();
}
@Override
public long getNativeConsumer() {
return 0;
}
@Override
public void quadTo(final float x1, final float y1,
final float xe, final float ye)
{
// Test if curve is too small:
float dx = (xe - cx);
float dy = (ye - cy);
if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) {
// check control points P1:
dx = (x1 - cx);
dy = (y1 - cy);
if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) {
return;
}
}
delegate.quadTo(x1, y1, xe, ye);
// final end point:
cx = xe;
cy = ye;
}
@Override
public void curveTo(final float x1, final float y1,
final float x2, final float y2,
final float xe, final float ye)
{
// Test if curve is too small:
float dx = (xe - cx);
float dy = (ye - cy);
if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) {
// check control points P1:
dx = (x1 - cx);
dy = (y1 - cy);
if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) {
// check control points P2:
dx = (x2 - cx);
dy = (y2 - cy);
if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) {
return;
}
}
}
delegate.curveTo(x1, y1, x2, y2, xe, ye);
// final end point:
cx = xe;
cy = ye;
}
@Override
public void moveTo(final float xe, final float ye) {
delegate.moveTo(xe, ye);
// starting point:
cx = xe;
cy = ye;
}
@Override
public void lineTo(final float xe, final float ye) {
// Test if segment is too small:
float dx = (xe - cx);
float dy = (ye - cy);
if ((dx * dx + dy * dy) <= SQUARE_TOLERANCE) {
return;
}
delegate.lineTo(xe, ye);
// final end point:
cx = xe;
cy = ye;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2018, 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
@ -54,9 +54,9 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
private static final int SUBPIXEL_TILE
= TILE_H << SUBPIXEL_LG_POSITIONS_Y;
// 2048 (pixelSize) pixels (height) x 8 subpixels = 64K
// 2176 pixels (height) x 8 subpixels = 68K
static final int INITIAL_BUCKET_ARRAY
= INITIAL_PIXEL_DIM * SUBPIXEL_POSITIONS_Y;
= INITIAL_PIXEL_HEIGHT * SUBPIXEL_POSITIONS_Y;
// crossing capacity = edges count / 4 ~ 1024
static final int INITIAL_CROSSING_COUNT = INITIAL_EDGES_COUNT >> 2;
@ -77,13 +77,17 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
// curve break into lines
// cubic error in subpixels to decrement step
private static final float CUB_DEC_ERR_SUBPIX
= MarlinProperties.getCubicDecD2() * (NORM_SUBPIXELS / 8.0f); // 1 pixel
= MarlinProperties.getCubicDecD2() * (SUBPIXEL_POSITIONS_X / 8.0f); // 1.0 / 8th pixel
// cubic error in subpixels to increment step
private static final float CUB_INC_ERR_SUBPIX
= MarlinProperties.getCubicIncD1() * (NORM_SUBPIXELS / 8.0f); // 0.4 pixel
= MarlinProperties.getCubicIncD1() * (SUBPIXEL_POSITIONS_X / 8.0f); // 0.4 / 8th pixel
// scale factor for Y-axis contribution to quad / cubic errors:
public static final float SCALE_DY = ((float) SUBPIXEL_POSITIONS_X) / SUBPIXEL_POSITIONS_Y;
// TestNonAARasterization (JDK-8170879): cubics
// bad paths (59294/100000 == 59,29%, 94335 bad pixels (avg = 1,59), 3966 warnings (avg = 0,07)
// 2018
// 1.0 / 0.2: bad paths (67194/100000 == 67,19%, 117394 bad pixels (avg = 1,75 - max = 9), 4042 warnings (avg = 0,06)
// cubic bind length to decrement step
public static final float CUB_DEC_BND
@ -110,10 +114,12 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
// quad break into lines
// quadratic error in subpixels
private static final float QUAD_DEC_ERR_SUBPIX
= MarlinProperties.getQuadDecD2() * (NORM_SUBPIXELS / 8.0f); // 0.5 pixel
= MarlinProperties.getQuadDecD2() * (SUBPIXEL_POSITIONS_X / 8.0f); // 0.5 / 8th pixel
// TestNonAARasterization (JDK-8170879): quads
// bad paths (62916/100000 == 62,92%, 103818 bad pixels (avg = 1,65), 6514 warnings (avg = 0,10)
// 2018
// 0.50px = bad paths (62915/100000 == 62,92%, 103810 bad pixels (avg = 1,65), 6512 warnings (avg = 0,10)
// quadratic bind length to decrement step
public static final float QUAD_DEC_BND
@ -180,7 +186,7 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
int count = 1; // dt = 1 / count
// maximum(ddX|Y) = norm(dbx, dby) * dt^2 (= 1)
float maxDD = Math.abs(c.dbx) + Math.abs(c.dby);
float maxDD = Math.abs(c.dbx) + Math.abs(c.dby) * SCALE_DY;
final float _DEC_BND = QUAD_DEC_BND;
@ -194,7 +200,8 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
}
}
int nL = 0; // line count
final int nL = count; // line count
if (count > 1) {
final float icount = 1.0f / count; // dt
final float icount2 = icount * icount; // dt^2
@ -204,17 +211,12 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
float dx = c.bx * icount2 + c.cx * icount;
float dy = c.by * icount2 + c.cy * icount;
float x1, y1;
while (--count > 0) {
x1 = x0 + dx;
dx += ddx;
y1 = y0 + dy;
dy += ddy;
// we use x0, y0 to walk the line
for (float x1 = x0, y1 = y0; --count > 0; dx += ddx, dy += ddy) {
x1 += dx;
y1 += dy;
addLine(x0, y0, x1, y1);
if (DO_STATS) { nL++; }
x0 = x1;
y0 = y1;
}
@ -222,7 +224,7 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
addLine(x0, y0, x2, y2);
if (DO_STATS) {
rdrCtx.stats.stat_rdr_quadBreak.add(nL + 1);
rdrCtx.stats.stat_rdr_quadBreak.add(nL);
}
}
@ -250,34 +252,20 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
dx = c.ax * icount3 + c.bx * icount2 + c.cx * icount;
dy = c.ay * icount3 + c.by * icount2 + c.cy * icount;
// we use x0, y0 to walk the line
float x1 = x0, y1 = y0;
int nL = 0; // line count
final float _DEC_BND = CUB_DEC_BND;
final float _INC_BND = CUB_INC_BND;
final float _SCALE_DY = SCALE_DY;
while (count > 0) {
// divide step by half:
while (Math.abs(ddx) + Math.abs(ddy) >= _DEC_BND) {
dddx /= 8.0f;
dddy /= 8.0f;
ddx = ddx / 4.0f - dddx;
ddy = ddy / 4.0f - dddy;
dx = (dx - ddx) / 2.0f;
dy = (dy - ddy) / 2.0f;
// we use x0, y0 to walk the line
for (float x1 = x0, y1 = y0; count > 0; ) {
// inc / dec => ratio ~ 5 to minimize upscale / downscale but minimize edges
count <<= 1;
if (DO_STATS) {
rdrCtx.stats.stat_rdr_curveBreak_dec.add(count);
}
}
// double step:
// float step:
// can only do this on even "count" values, because we must divide count by 2
while (count % 2 == 0
&& Math.abs(dx) + Math.abs(dy) <= _INC_BND)
{
while ((count % 2 == 0)
&& ((Math.abs(ddx) + Math.abs(ddy) * _SCALE_DY) <= _INC_BND)) {
dx = 2.0f * dx + ddx;
dy = 2.0f * dy + ddy;
ddx = 4.0f * (ddx + dddx);
@ -290,26 +278,40 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
rdrCtx.stats.stat_rdr_curveBreak_inc.add(count);
}
}
if (--count > 0) {
x1 += dx;
dx += ddx;
ddx += dddx;
y1 += dy;
dy += ddy;
ddy += dddy;
} else {
x1 = x3;
y1 = y3;
// divide step by half:
while ((Math.abs(ddx) + Math.abs(ddy) * _SCALE_DY) >= _DEC_BND) {
dddx /= 8.0f;
dddy /= 8.0f;
ddx = ddx / 4.0f - dddx;
ddy = ddy / 4.0f - dddy;
dx = (dx - ddx) / 2.0f;
dy = (dy - ddy) / 2.0f;
count <<= 1;
if (DO_STATS) {
rdrCtx.stats.stat_rdr_curveBreak_dec.add(count);
}
}
if (--count == 0) {
break;
}
addLine(x0, y0, x1, y1);
x1 += dx;
y1 += dy;
dx += ddx;
dy += ddy;
ddx += dddx;
ddy += dddy;
if (DO_STATS) { nL++; }
addLine(x0, y0, x1, y1);
x0 = x1;
y0 = y1;
}
addLine(x0, y0, x3, y3);
if (DO_STATS) {
rdrCtx.stats.stat_rdr_curveBreak.add(nL);
rdrCtx.stats.stat_rdr_curveBreak.add(nL + 1);
}
}
@ -537,8 +539,8 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
edgeBuckets = edgeBuckets_ref.initial;
edgeBucketCounts = edgeBucketCounts_ref.initial;
// 2048 (pixelsize) pixel large
alphaLine_ref = rdrCtx.newCleanIntArrayRef(INITIAL_AA_ARRAY); // 8K
// 4096 pixels large
alphaLine_ref = rdrCtx.newCleanIntArrayRef(INITIAL_AA_ARRAY); // 16K
alphaLine = alphaLine_ref.initial;
crossings_ref = rdrCtx.newDirtyIntArrayRef(INITIAL_CROSSING_COUNT); // 2K
@ -696,8 +698,10 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
{
final float xe = tosubpixx(pix_x3);
final float ye = tosubpixy(pix_y3);
curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1),
tosubpixx(pix_x2), tosubpixy(pix_y2), xe, ye);
curve.set(x0, y0,
tosubpixx(pix_x1), tosubpixy(pix_y1),
tosubpixx(pix_x2), tosubpixy(pix_y2),
xe, ye);
curveBreakIntoLinesAndAdd(x0, y0, curve, xe, ye);
x0 = xe;
y0 = ye;
@ -709,7 +713,9 @@ final class Renderer implements PathConsumer2D, MarlinRenderer {
{
final float xe = tosubpixx(pix_x2);
final float ye = tosubpixy(pix_y2);
curve.set(x0, y0, tosubpixx(pix_x1), tosubpixy(pix_y1), xe, ye);
curve.set(x0, y0,
tosubpixx(pix_x1), tosubpixy(pix_y1),
xe, ye);
quadBreakIntoLinesAndAdd(x0, y0, curve, xe, ye);
x0 = xe;
y0 = ye;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2018, 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
@ -31,6 +31,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import sun.java2d.ReentrantContext;
import sun.java2d.marlin.ArrayCacheConst.CacheStats;
import sun.java2d.marlin.MarlinRenderingEngine.NormalizingPathIterator;
import sun.java2d.marlin.TransformingPathConsumer2D.CurveBasicMonotonizer;
import sun.java2d.marlin.TransformingPathConsumer2D.CurveClipSplitter;
/**
* This class is a renderer context dedicated to a single thread
@ -70,6 +72,8 @@ final class RendererContext extends ReentrantContext implements IRendererContext
final Stroker stroker;
// Simplifies out collinear lines
final CollinearSimplifier simplifier = new CollinearSimplifier();
// Simplifies path
final PathSimplifier pathSimplifier = new PathSimplifier();
final Dasher dasher;
final MarlinTileGenerator ptg;
final MarlinCache cache;
@ -81,6 +85,10 @@ final class RendererContext extends ReentrantContext implements IRendererContext
boolean closedPath = false;
// clip rectangle (ymin, ymax, xmin, xmax):
final float[] clipRect = new float[4];
// CurveBasicMonotonizer instance
final CurveBasicMonotonizer monotonizer;
// CurveClipSplitter instance
final CurveClipSplitter curveClipSplitter;
// Array caches:
/* clean int[] cache (zero-filled) = 5 refs */
@ -121,6 +129,10 @@ final class RendererContext extends ReentrantContext implements IRendererContext
nPCPathIterator = new NormalizingPathIterator.NearestPixelCenter(float6);
nPQPathIterator = new NormalizingPathIterator.NearestPixelQuarter(float6);
// curve monotonizer & clip subdivider (before transformerPC2D init)
monotonizer = new CurveBasicMonotonizer(this);
curveClipSplitter = new CurveClipSplitter(this);
// MarlinRenderingEngine.TransformingPathConsumer2D
transformerPC2D = new TransformingPathConsumer2D(this);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2018, 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
@ -36,7 +36,6 @@ import static sun.java2d.marlin.MarlinUtils.logInfo;
import sun.java2d.marlin.stats.Histogram;
import sun.java2d.marlin.stats.Monitor;
import sun.java2d.marlin.stats.StatLong;
import sun.awt.util.ThreadGroupUtils;
/**
* This class gathers global rendering statistics for debugging purposes only
@ -359,7 +358,7 @@ public final class RendererStats implements MarlinConst {
AccessController.doPrivileged(
(PrivilegedAction<Void>) () -> {
final Thread hook = new Thread(
ThreadGroupUtils.getRootThreadGroup(),
MarlinUtils.getRootThreadGroup(),
new Runnable() {
@Override
public void run() {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2018, 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
@ -29,6 +29,8 @@ import java.util.Arrays;
import sun.awt.geom.PathConsumer2D;
import sun.java2d.marlin.Helpers.PolyStack;
import sun.java2d.marlin.TransformingPathConsumer2D.CurveBasicMonotonizer;
import sun.java2d.marlin.TransformingPathConsumer2D.CurveClipSplitter;
// TODO: some of the arithmetic here is too verbose and prone to hard to
// debug typos. We should consider making a small Point/Vector class that
@ -39,10 +41,9 @@ final class Stroker implements PathConsumer2D, MarlinConst {
private static final int DRAWING_OP_TO = 1; // ie. curve, line, or quad
private static final int CLOSE = 2;
// pisces used to use fixed point arithmetic with 16 decimal digits. I
// didn't want to change the values of the constant 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.0f/65536.0f;
// round join threshold = 1 subpixel
private static final float ERR_JOIN = (1.0f / MIN_SUBPIXELS);
private static final float ROUND_JOIN_THRESHOLD = ERR_JOIN * ERR_JOIN;
// kappa = (4/3) * (SQRT(2) - 1)
private static final float C = (float)(4.0d * (Math.sqrt(2.0d) - 1.0d) / 3.0d);
@ -50,8 +51,6 @@ final class Stroker implements PathConsumer2D, MarlinConst {
// SQRT(2)
private static final float SQRT_2 = (float)Math.sqrt(2.0d);
private static final int MAX_N_CURVES = 11;
private PathConsumer2D out;
private int capStyle;
@ -82,12 +81,8 @@ final class Stroker implements PathConsumer2D, MarlinConst {
private final PolyStack reverse;
// This is where the curve to be processed is put. We give it
// enough room to store all curves.
private final float[] middle = new float[MAX_N_CURVES * 6 + 2];
private final float[] lp = new float[8];
private final float[] rp = new float[8];
private final float[] subdivTs = new float[MAX_N_CURVES - 1];
// per-thread renderer context
final RendererContext rdrCtx;
@ -108,6 +103,11 @@ final class Stroker implements PathConsumer2D, MarlinConst {
private boolean opened = false;
// flag indicating if the starting point's cap is done
private boolean capStart = false;
// flag indicating to monotonize curves
private boolean monotonize;
private boolean subdivide = DO_CLIP_SUBDIVIDER;
private final CurveClipSplitter curveSplitter;
/**
* Constructs a <code>Stroker</code>.
@ -126,6 +126,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
: new PolyStack(rdrCtx);
this.curve = rdrCtx.curve;
this.curveSplitter = rdrCtx.curveClipSplitter;
}
/**
@ -141,6 +142,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
* <code>JOIN_BEVEL</code>.
* @param miterLimit the desired miter limit
* @param scale scaling factor applied to clip boundaries
* @param subdivideCurves true to indicate to subdivide curves, false if dasher does
* @return this instance
*/
Stroker init(final PathConsumer2D pc2d,
@ -148,12 +150,15 @@ final class Stroker implements PathConsumer2D, MarlinConst {
final int capStyle,
final int joinStyle,
final float miterLimit,
final float scale)
final float scale,
final boolean subdivideCurves)
{
this.out = pc2d;
this.lineWidth2 = lineWidth / 2.0f;
this.invHalfLineWidth2Sq = 1.0f / (2.0f * lineWidth2 * lineWidth2);
this.monotonize = subdivideCurves;
this.capStyle = capStyle;
this.joinStyle = joinStyle;
@ -191,6 +196,15 @@ final class Stroker implements PathConsumer2D, MarlinConst {
_clipRect[2] -= margin - rdrOffX;
_clipRect[3] += margin + rdrOffX;
this.clipRect = _clipRect;
// initialize curve splitter here for stroker & dasher:
if (DO_CLIP_SUBDIVIDER) {
subdivide = subdivideCurves;
// adjust padded clip rectangle:
curveSplitter.init();
} else {
subdivide = false;
}
} else {
this.clipRect = null;
this.cOutCode = 0;
@ -199,6 +213,12 @@ final class Stroker implements PathConsumer2D, MarlinConst {
return this; // fluent API
}
void disableClipping() {
this.clipRect = null;
this.cOutCode = 0;
this.sOutCode = 0;
}
/**
* Disposes this stroker:
* clean up before reusing this instance
@ -215,10 +235,8 @@ final class Stroker implements PathConsumer2D, MarlinConst {
Arrays.fill(offset1, 0.0f);
Arrays.fill(offset2, 0.0f);
Arrays.fill(miter, 0.0f);
Arrays.fill(middle, 0.0f);
Arrays.fill(lp, 0.0f);
Arrays.fill(rp, 0.0f);
Arrays.fill(subdivTs, 0.0f);
}
}
@ -250,19 +268,20 @@ final class Stroker implements PathConsumer2D, MarlinConst {
return dx1 * dy2 <= dy1 * dx2;
}
private void drawRoundJoin(float x, float y,
float omx, float omy, float mx, float my,
boolean rev,
float threshold)
private void mayDrawRoundJoin(float cx, float cy,
float omx, float omy,
float mx, float my,
boolean rev)
{
if ((omx == 0.0f && omy == 0.0f) || (mx == 0.0f && my == 0.0f)) {
return;
}
float domx = omx - mx;
float domy = omy - my;
float len = domx*domx + domy*domy;
if (len < threshold) {
final float domx = omx - mx;
final float domy = omy - my;
final float lenSq = domx*domx + domy*domy;
if (lenSq < ROUND_JOIN_THRESHOLD) {
return;
}
@ -272,7 +291,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
mx = -mx;
my = -my;
}
drawRoundJoin(x, y, omx, omy, mx, my, rev);
drawRoundJoin(cx, cy, omx, omy, mx, my, rev);
}
private void drawRoundJoin(float cx, float cy,
@ -383,7 +402,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
final float x1, final float y1,
final float x0p, final float y0p,
final float x1p, final float y1p,
final float[] m, int off)
final float[] m)
{
float x10 = x1 - x0;
float y10 = y1 - y0;
@ -402,8 +421,8 @@ final class Stroker implements PathConsumer2D, MarlinConst {
float den = x10*y10p - x10p*y10;
float t = x10p*(y0-y0p) - y10p*(x0-x0p);
t /= den;
m[off++] = x0 + t*x10;
m[off] = y0 + t*y10;
m[0] = x0 + t*x10;
m[1] = y0 + t*y10;
}
// Return the intersection point of the lines (x0, y0) -> (x1, y1)
@ -412,7 +431,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
final float x1, final float y1,
final float x0p, final float y0p,
final float x1p, final float y1p,
final float[] m, int off)
final float[] m)
{
float x10 = x1 - x0;
float y10 = y1 - y0;
@ -430,20 +449,21 @@ final class Stroker implements PathConsumer2D, MarlinConst {
// immediately).
float den = x10*y10p - x10p*y10;
if (den == 0.0f) {
m[off++] = (x0 + x0p) / 2.0f;
m[off] = (y0 + y0p) / 2.0f;
return;
m[2] = (x0 + x0p) / 2.0f;
m[3] = (y0 + y0p) / 2.0f;
} else {
float t = x10p*(y0-y0p) - y10p*(x0-x0p);
t /= den;
m[2] = x0 + t*x10;
m[3] = y0 + t*y10;
}
float t = x10p*(y0-y0p) - y10p*(x0-x0p);
t /= den;
m[off++] = x0 + t*x10;
m[off] = y0 + t*y10;
}
private void drawMiter(final float pdx, final float pdy,
final float x0, final float y0,
final float dx, final float dy,
float omx, float omy, float mx, float my,
float omx, float omy,
float mx, float my,
boolean rev)
{
if ((mx == omx && my == omy) ||
@ -461,8 +481,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
}
computeMiter((x0 - pdx) + omx, (y0 - pdy) + omy, x0 + omx, y0 + omy,
(dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my,
miter, 0);
(dx + x0) + mx, (dy + y0) + my, x0 + mx, y0 + my, miter);
final float miterX = miter[0];
final float miterY = miter[1];
@ -480,7 +499,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
@Override
public void moveTo(final float x0, final float y0) {
moveTo(x0, y0, cOutCode);
_moveTo(x0, y0, cOutCode);
// update starting point:
this.sx0 = x0;
this.sy0 = y0;
@ -496,7 +515,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
}
}
private void moveTo(final float x0, final float y0,
private void _moveTo(final float x0, final float y0,
final int outcode)
{
if (prev == MOVE_TO) {
@ -523,16 +542,40 @@ final class Stroker implements PathConsumer2D, MarlinConst {
final boolean force)
{
final int outcode0 = this.cOutCode;
if (!force && clipRect != null) {
final int outcode1 = Helpers.outcode(x1, y1, clipRect);
this.cOutCode = outcode1;
// basic rejection criteria
if ((outcode0 & outcode1) != 0) {
moveTo(x1, y1, outcode0);
opened = true;
return;
// Should clip
final int orCode = (outcode0 | outcode1);
if (orCode != 0) {
final int sideCode = outcode0 & outcode1;
// basic rejection criteria:
if (sideCode == 0) {
// ovelap clip:
if (subdivide) {
// avoid reentrance
subdivide = false;
// subdivide curve => callback with subdivided parts:
boolean ret = curveSplitter.splitLine(cx0, cy0, x1, y1,
orCode, this);
// reentrance is done:
subdivide = true;
if (ret) {
return;
}
}
// already subdivided so render it
} else {
this.cOutCode = outcode1;
_moveTo(x1, y1, outcode0);
opened = true;
return;
}
}
this.cOutCode = outcode1;
}
float dx = x1 - cx0;
@ -754,10 +797,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
if (joinStyle == JOIN_MITER) {
drawMiter(pdx, pdy, x0, y0, dx, dy, omx, omy, mx, my, cw);
} else if (joinStyle == JOIN_ROUND) {
drawRoundJoin(x0, y0,
omx, omy,
mx, my, cw,
ROUND_JOIN_THRESHOLD);
mayDrawRoundJoin(x0, y0, omx, omy, mx, my, cw);
}
}
emitLineTo(x0, y0, !cw);
@ -767,18 +807,19 @@ final class Stroker implements PathConsumer2D, MarlinConst {
private static boolean within(final float x1, final float y1,
final float x2, final float y2,
final float ERR)
final float err)
{
assert ERR > 0 : "";
assert err > 0 : "";
// compare taxicab distance. ERR will always be small, so using
// true distance won't give much benefit
return (Helpers.within(x1, x2, ERR) && // we want to avoid calling Math.abs
Helpers.within(y1, y2, ERR)); // this is just as good.
return (Helpers.within(x1, x2, err) && // we want to avoid calling Math.abs
Helpers.within(y1, y2, err)); // this is just as good.
}
private void getLineOffsets(float x1, float y1,
float x2, float y2,
float[] left, float[] right) {
private void getLineOffsets(final float x1, final float y1,
final float x2, final float y2,
final float[] left, final float[] right)
{
computeOffset(x2 - x1, y2 - y1, lineWidth2, offset0);
final float mx = offset0[0];
final float my = offset0[1];
@ -786,14 +827,16 @@ final class Stroker implements PathConsumer2D, MarlinConst {
left[1] = y1 + my;
left[2] = x2 + mx;
left[3] = y2 + my;
right[0] = x1 - mx;
right[1] = y1 - my;
right[2] = x2 - mx;
right[3] = y2 - my;
}
private int computeOffsetCubic(float[] pts, final int off,
float[] leftOff, float[] rightOff)
private int computeOffsetCubic(final float[] pts, final int off,
final float[] leftOff,
final float[] rightOff)
{
// if p1=p2 or p3=p4 it means that the derivative at the endpoint
// vanishes, which creates problems with computeOffset. Usually
@ -802,7 +845,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
// the input curve at the cusp, and passes it to this function.
// because of inaccuracies in the splitting, we consider points
// equal if they're very close to each other.
final float x1 = pts[off + 0], y1 = pts[off + 1];
final float x1 = pts[off ], y1 = pts[off + 1];
final float x2 = pts[off + 2], y2 = pts[off + 3];
final float x3 = pts[off + 4], y3 = pts[off + 5];
final float x4 = pts[off + 6], y4 = pts[off + 7];
@ -816,6 +859,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
// in which case ignore if p1 == p2
final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0f * Math.ulp(y2));
final boolean p3eqp4 = within(x3, y3, x4, y4, 6.0f * Math.ulp(y4));
if (p1eqp2 && p3eqp4) {
getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
return 4;
@ -831,6 +875,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
float dotsq = (dx1 * dx4 + dy1 * dy4);
dotsq *= dotsq;
float l1sq = dx1 * dx1 + dy1 * dy1, l4sq = dx4 * dx4 + dy4 * dy4;
if (Helpers.within(dotsq, l1sq * l4sq, 4.0f * Math.ulp(dotsq))) {
getLineOffsets(x1, y1, x4, y4, leftOff, rightOff);
return 4;
@ -944,10 +989,11 @@ final class Stroker implements PathConsumer2D, MarlinConst {
// compute offset curves using bezier spline through t=0.5 (i.e.
// ComputedCurve(0.5) == IdealParallelCurve(0.5))
// return the kind of curve in the right and left arrays.
private int computeOffsetQuad(float[] pts, final int off,
float[] leftOff, float[] rightOff)
private int computeOffsetQuad(final float[] pts, final int off,
final float[] leftOff,
final float[] rightOff)
{
final float x1 = pts[off + 0], y1 = pts[off + 1];
final float x1 = pts[off ], y1 = pts[off + 1];
final float x2 = pts[off + 2], y2 = pts[off + 3];
final float x3 = pts[off + 4], y3 = pts[off + 5];
@ -968,6 +1014,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
// in which case ignore.
final boolean p1eqp2 = within(x1, y1, x2, y2, 6.0f * Math.ulp(y2));
final boolean p2eqp3 = within(x2, y2, x3, y3, 6.0f * Math.ulp(y3));
if (p1eqp2 || p2eqp3) {
getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
return 4;
@ -977,6 +1024,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
float dotsq = (dx1 * dx3 + dy1 * dy3);
dotsq *= dotsq;
float l1sq = dx1 * dx1 + dy1 * dy1, l3sq = dx3 * dx3 + dy3 * dy3;
if (Helpers.within(dotsq, l1sq * l3sq, 4.0f * Math.ulp(dotsq))) {
getLineOffsets(x1, y1, x3, y3, leftOff, rightOff);
return 4;
@ -992,151 +1040,111 @@ final class Stroker implements PathConsumer2D, MarlinConst {
float y1p = y1 + offset0[1]; // point
float x3p = x3 + offset1[0]; // end
float y3p = y3 + offset1[1]; // point
safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff, 2);
safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, leftOff);
leftOff[0] = x1p; leftOff[1] = y1p;
leftOff[4] = x3p; leftOff[5] = y3p;
x1p = x1 - offset0[0]; y1p = y1 - offset0[1];
x3p = x3 - offset1[0]; y3p = y3 - offset1[1];
safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff, 2);
safeComputeMiter(x1p, y1p, x1p+dx1, y1p+dy1, x3p, y3p, x3p-dx3, y3p-dy3, rightOff);
rightOff[0] = x1p; rightOff[1] = y1p;
rightOff[4] = x3p; rightOff[5] = y3p;
return 6;
}
// finds values of t where the curve in pts should be subdivided in order
// to get good offset curves a distance of w away from the middle curve.
// Stores the points in ts, and returns how many of them there were.
private static int findSubdivPoints(final Curve c, float[] pts, float[] ts,
final int type, final float w)
{
final float x12 = pts[2] - pts[0];
final float y12 = pts[3] - pts[1];
// if the curve is already parallel to either axis we gain nothing
// from rotating it.
if (y12 != 0.0f && x12 != 0.0f) {
// we rotate it so that the first vector in the control polygon is
// parallel to the x-axis. This will ensure that rotated quarter
// circles won't be subdivided.
final float hypot = (float) Math.sqrt(x12 * x12 + y12 * y12);
final float cos = x12 / hypot;
final float sin = y12 / hypot;
final float x1 = cos * pts[0] + sin * pts[1];
final float y1 = cos * pts[1] - sin * pts[0];
final float x2 = cos * pts[2] + sin * pts[3];
final float y2 = cos * pts[3] - sin * pts[2];
final float x3 = cos * pts[4] + sin * pts[5];
final float y3 = cos * pts[5] - sin * pts[4];
switch(type) {
case 8:
final float x4 = cos * pts[6] + sin * pts[7];
final float y4 = cos * pts[7] - sin * pts[6];
c.set(x1, y1, x2, y2, x3, y3, x4, y4);
break;
case 6:
c.set(x1, y1, x2, y2, x3, y3);
break;
default:
}
} else {
c.set(pts, type);
}
int ret = 0;
// we subdivide at values of t such that the remaining rotated
// curves are monotonic in x and y.
ret += c.dxRoots(ts, ret);
ret += c.dyRoots(ts, ret);
// subdivide at inflection points.
if (type == 8) {
// quadratic curves can't have inflection points
ret += c.infPoints(ts, ret);
}
// now we must subdivide at points where one of the offset curves will have
// a cusp. This happens at ts where the radius of curvature is equal to w.
ret += c.rootsOfROCMinusW(ts, ret, w, 0.0001f);
ret = Helpers.filterOutNotInAB(ts, 0, ret, 0.0001f, 0.9999f);
Helpers.isort(ts, 0, ret);
return ret;
}
@Override
public void curveTo(final float x1, final float y1,
final float x2, final float y2,
final float x3, final float y3)
{
final int outcode0 = this.cOutCode;
if (clipRect != null) {
final int outcode1 = Helpers.outcode(x1, y1, clipRect);
final int outcode2 = Helpers.outcode(x2, y2, clipRect);
final int outcode3 = Helpers.outcode(x3, y3, clipRect);
this.cOutCode = outcode3;
if ((outcode0 & outcode3) != 0) {
final int outcode1 = Helpers.outcode(x1, y1, clipRect);
final int outcode2 = Helpers.outcode(x2, y2, clipRect);
// Should clip
final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);
if (orCode != 0) {
final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;
// basic rejection criteria
if ((outcode0 & outcode1 & outcode2 & outcode3) != 0) {
moveTo(x3, y3, outcode0);
// basic rejection criteria:
if (sideCode == 0) {
// ovelap clip:
if (subdivide) {
// avoid reentrance
subdivide = false;
// subdivide curve => callback with subdivided parts:
boolean ret = curveSplitter.splitCurve(cx0, cy0, x1, y1,
x2, y2, x3, y3,
orCode, this);
// reentrance is done:
subdivide = true;
if (ret) {
return;
}
}
// already subdivided so render it
} else {
this.cOutCode = outcode3;
_moveTo(x3, y3, outcode0);
opened = true;
return;
}
}
this.cOutCode = outcode3;
}
_curveTo(x1, y1, x2, y2, x3, y3, outcode0);
}
final float[] mid = middle;
mid[0] = cx0; mid[1] = cy0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
mid[6] = x3; mid[7] = y3;
private void _curveTo(final float x1, final float y1,
final float x2, final float y2,
final float x3, final float y3,
final int outcode0)
{
// need these so we can update the state at the end of this method
final float xf = x3, yf = y3;
float dxs = mid[2] - mid[0];
float dys = mid[3] - mid[1];
float dxf = mid[6] - mid[4];
float dyf = mid[7] - mid[5];
float dxs = x1 - cx0;
float dys = y1 - cy0;
float dxf = x3 - x2;
float dyf = y3 - y2;
boolean p1eqp2 = (dxs == 0.0f && dys == 0.0f);
boolean p3eqp4 = (dxf == 0.0f && dyf == 0.0f);
if (p1eqp2) {
dxs = mid[4] - mid[0];
dys = mid[5] - mid[1];
if (dxs == 0.0f && dys == 0.0f) {
dxs = mid[6] - mid[0];
dys = mid[7] - mid[1];
if ((dxs == 0.0f) && (dys == 0.0f)) {
dxs = x2 - cx0;
dys = y2 - cy0;
if ((dxs == 0.0f) && (dys == 0.0f)) {
dxs = x3 - cx0;
dys = y3 - cy0;
}
}
if (p3eqp4) {
dxf = mid[6] - mid[2];
dyf = mid[7] - mid[3];
if (dxf == 0.0f && dyf == 0.0f) {
dxf = mid[6] - mid[0];
dyf = mid[7] - mid[1];
if ((dxf == 0.0f) && (dyf == 0.0f)) {
dxf = x3 - x1;
dyf = y3 - y1;
if ((dxf == 0.0f) && (dyf == 0.0f)) {
dxf = x3 - cx0;
dyf = y3 - cy0;
}
}
if (dxs == 0.0f && dys == 0.0f) {
if ((dxs == 0.0f) && (dys == 0.0f)) {
// this happens if the "curve" is just a point
// fix outcode0 for lineTo() call:
if (clipRect != null) {
this.cOutCode = outcode0;
}
lineTo(mid[0], mid[1]);
lineTo(cx0, cy0);
return;
}
// if these vectors are too small, normalize them, to avoid future
// precision problems.
if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
float len = (float) Math.sqrt(dxs*dxs + dys*dys);
final float len = (float)Math.sqrt(dxs * dxs + dys * dys);
dxs /= len;
dys /= len;
}
if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
float len = (float) Math.sqrt(dxf*dxf + dyf*dyf);
final float len = (float)Math.sqrt(dxf * dxf + dyf * dyf);
dxf /= len;
dyf /= len;
}
@ -1144,17 +1152,25 @@ final class Stroker implements PathConsumer2D, MarlinConst {
computeOffset(dxs, dys, lineWidth2, offset0);
drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
final int nSplits = findSubdivPoints(curve, mid, subdivTs, 8, lineWidth2);
float prevT = 0.0f;
for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
final float t = subdivTs[i];
Helpers.subdivideCubicAt((t - prevT) / (1.0f - prevT),
mid, off, mid, off, mid, off + 6);
prevT = t;
}
int nSplits = 0;
final float[] mid;
final float[] l = lp;
if (monotonize) {
// monotonize curve:
final CurveBasicMonotonizer monotonizer
= rdrCtx.monotonizer.curve(cx0, cy0, x1, y1, x2, y2, x3, y3);
nSplits = monotonizer.nbSplits;
mid = monotonizer.middle;
} else {
// use left instead:
mid = l;
mid[0] = cx0; mid[1] = cy0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
mid[6] = x3; mid[7] = y3;
}
final float[] r = rp;
int kind = 0;
@ -1178,8 +1194,8 @@ final class Stroker implements PathConsumer2D, MarlinConst {
}
this.prev = DRAWING_OP_TO;
this.cx0 = xf;
this.cy0 = yf;
this.cx0 = x3;
this.cy0 = y3;
this.cdx = dxf;
this.cdy = dyf;
this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;
@ -1191,74 +1207,101 @@ final class Stroker implements PathConsumer2D, MarlinConst {
final float x2, final float y2)
{
final int outcode0 = this.cOutCode;
if (clipRect != null) {
final int outcode1 = Helpers.outcode(x1, y1, clipRect);
final int outcode2 = Helpers.outcode(x2, y2, clipRect);
this.cOutCode = outcode2;
if ((outcode0 & outcode2) != 0) {
final int outcode1 = Helpers.outcode(x1, y1, clipRect);
// Should clip
final int orCode = (outcode0 | outcode1 | outcode2);
if (orCode != 0) {
final int sideCode = outcode0 & outcode1 & outcode2;
// basic rejection criteria
if ((outcode0 & outcode1 & outcode2) != 0) {
moveTo(x2, y2, outcode0);
// basic rejection criteria:
if (sideCode == 0) {
// ovelap clip:
if (subdivide) {
// avoid reentrance
subdivide = false;
// subdivide curve => call lineTo() with subdivided curves:
boolean ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,
x2, y2, orCode, this);
// reentrance is done:
subdivide = true;
if (ret) {
return;
}
}
// already subdivided so render it
} else {
this.cOutCode = outcode2;
_moveTo(x2, y2, outcode0);
opened = true;
return;
}
}
this.cOutCode = outcode2;
}
_quadTo(x1, y1, x2, y2, outcode0);
}
final float[] mid = middle;
mid[0] = cx0; mid[1] = cy0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
private void _quadTo(final float x1, final float y1,
final float x2, final float y2,
final int outcode0)
{
// need these so we can update the state at the end of this method
final float xf = x2, yf = y2;
float dxs = mid[2] - mid[0];
float dys = mid[3] - mid[1];
float dxf = mid[4] - mid[2];
float dyf = mid[5] - mid[3];
if ((dxs == 0.0f && dys == 0.0f) || (dxf == 0.0f && dyf == 0.0f)) {
dxs = dxf = mid[4] - mid[0];
dys = dyf = mid[5] - mid[1];
float dxs = x1 - cx0;
float dys = y1 - cy0;
float dxf = x2 - x1;
float dyf = y2 - y1;
if (((dxs == 0.0f) && (dys == 0.0f)) || ((dxf == 0.0f) && (dyf == 0.0f))) {
dxs = dxf = x2 - cx0;
dys = dyf = y2 - cy0;
}
if (dxs == 0.0f && dys == 0.0f) {
if ((dxs == 0.0f) && (dys == 0.0f)) {
// this happens if the "curve" is just a point
// fix outcode0 for lineTo() call:
if (clipRect != null) {
this.cOutCode = outcode0;
}
lineTo(mid[0], mid[1]);
lineTo(cx0, cy0);
return;
}
// if these vectors are too small, normalize them, to avoid future
// precision problems.
if (Math.abs(dxs) < 0.1f && Math.abs(dys) < 0.1f) {
float len = (float) Math.sqrt(dxs*dxs + dys*dys);
final float len = (float)Math.sqrt(dxs * dxs + dys * dys);
dxs /= len;
dys /= len;
}
if (Math.abs(dxf) < 0.1f && Math.abs(dyf) < 0.1f) {
float len = (float) Math.sqrt(dxf*dxf + dyf*dyf);
final float len = (float)Math.sqrt(dxf * dxf + dyf * dyf);
dxf /= len;
dyf /= len;
}
computeOffset(dxs, dys, lineWidth2, offset0);
drawJoin(cdx, cdy, cx0, cy0, dxs, dys, cmx, cmy, offset0[0], offset0[1], outcode0);
int nSplits = findSubdivPoints(curve, mid, subdivTs, 6, lineWidth2);
float prevt = 0.0f;
for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
final float t = subdivTs[i];
Helpers.subdivideQuadAt((t - prevt) / (1.0f - prevt),
mid, off, mid, off, mid, off + 4);
prevt = t;
}
int nSplits = 0;
final float[] mid;
final float[] l = lp;
if (monotonize) {
// monotonize quad:
final CurveBasicMonotonizer monotonizer
= rdrCtx.monotonizer.quad(cx0, cy0, x1, y1, x2, y2);
nSplits = monotonizer.nbSplits;
mid = monotonizer.middle;
} else {
// use left instead:
mid = l;
mid[0] = cx0; mid[1] = cy0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
}
final float[] r = rp;
int kind = 0;
@ -1282,8 +1325,8 @@ final class Stroker implements PathConsumer2D, MarlinConst {
}
this.prev = DRAWING_OP_TO;
this.cx0 = xf;
this.cy0 = yf;
this.cx0 = x2;
this.cy0 = y2;
this.cdx = dxf;
this.cdy = dyf;
this.cmx = (l[kind - 2] - r[kind - 2]) / 2.0f;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2018, 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
@ -28,11 +28,15 @@ package sun.java2d.marlin;
import sun.awt.geom.PathConsumer2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.util.Arrays;
import sun.java2d.marlin.Helpers.IndexStack;
import sun.java2d.marlin.Helpers.PolyStack;
final class TransformingPathConsumer2D {
// higher uncertainty in float variant for huge shapes > 10^7
static final float CLIP_RECT_PADDING = 1.0f;
private final RendererContext rdrCtx;
// recycled ClosedPathDetector instance from detectClosedPath()
@ -57,6 +61,7 @@ final class TransformingPathConsumer2D {
private final PathTracer tracerCPDetector = new PathTracer("ClosedPathDetector");
private final PathTracer tracerFiller = new PathTracer("Filler");
private final PathTracer tracerStroker = new PathTracer("Stroker");
private final PathTracer tracerDasher = new PathTracer("Dasher");
TransformingPathConsumer2D(final RendererContext rdrCtx) {
// used by RendererContext
@ -85,6 +90,10 @@ final class TransformingPathConsumer2D {
return tracerStroker.init(out);
}
PathConsumer2D traceDasher(PathConsumer2D out) {
return tracerDasher.init(out);
}
PathConsumer2D detectClosedPath(PathConsumer2D out) {
return cpDetector.init(out);
}
@ -500,11 +509,19 @@ final class TransformingPathConsumer2D {
private boolean outside = false;
// The current point OUTSIDE
// The current point (TODO stupid repeated info)
private float cx0, cy0;
// The current point OUTSIDE
private float cox0, coy0;
private boolean subdivide = MarlinConst.DO_CLIP_SUBDIVIDER;
private final CurveClipSplitter curveSplitter;
PathClipFilter(final RendererContext rdrCtx) {
this.clipRect = rdrCtx.clipRect;
this.curveSplitter = rdrCtx.curveClipSplitter;
this.stack = (rdrCtx.stats != null) ?
new IndexStack(rdrCtx,
rdrCtx.stats.stat_pcf_idxstack_indices,
@ -529,6 +546,11 @@ final class TransformingPathConsumer2D {
_clipRect[2] -= margin - rdrOffX;
_clipRect[3] += margin + rdrOffX;
if (MarlinConst.DO_CLIP_SUBDIVIDER) {
// adjust padded clip rectangle:
curveSplitter.init();
}
this.init_corners = true;
this.gOutCode = MarlinConst.OUTCODE_MASK_T_B_L_R;
@ -579,7 +601,9 @@ final class TransformingPathConsumer2D {
}
stack.pullAll(corners, out);
}
out.lineTo(cx0, cy0);
out.lineTo(cox0, coy0);
this.cx0 = cox0;
this.cy0 = coy0;
}
@Override
@ -604,38 +628,68 @@ final class TransformingPathConsumer2D {
public void moveTo(final float x0, final float y0) {
finishPath();
final int outcode = Helpers.outcode(x0, y0, clipRect);
this.cOutCode = outcode;
this.cOutCode = Helpers.outcode(x0, y0, clipRect);
this.outside = false;
out.moveTo(x0, y0);
this.cx0 = x0;
this.cy0 = y0;
}
@Override
public void lineTo(final float xe, final float ye) {
final int outcode0 = this.cOutCode;
final int outcode1 = Helpers.outcode(xe, ye, clipRect);
this.cOutCode = outcode1;
final int sideCode = (outcode0 & outcode1);
// Should clip
final int orCode = (outcode0 | outcode1);
if (orCode != 0) {
final int sideCode = (outcode0 & outcode1);
// basic rejection criteria:
if (sideCode == 0) {
this.gOutCode = 0;
} else {
this.gOutCode &= sideCode;
// keep last point coordinate before entering the clip again:
this.outside = true;
this.cx0 = xe;
this.cy0 = ye;
// basic rejection criteria:
if (sideCode == 0) {
// ovelap clip:
if (subdivide) {
// avoid reentrance
subdivide = false;
boolean ret;
// subdivide curve => callback with subdivided parts:
if (outside) {
ret = curveSplitter.splitLine(cox0, coy0, xe, ye,
orCode, this);
} else {
ret = curveSplitter.splitLine(cx0, cy0, xe, ye,
orCode, this);
}
// reentrance is done:
subdivide = true;
if (ret) {
return;
}
}
// already subdivided so render it
} else {
this.cOutCode = outcode1;
this.gOutCode &= sideCode;
// keep last point coordinate before entering the clip again:
this.outside = true;
this.cox0 = xe;
this.coy0 = ye;
clip(sideCode, outcode0, outcode1);
return;
clip(sideCode, outcode0, outcode1);
return;
}
}
this.cOutCode = outcode1;
this.gOutCode = 0;
if (outside) {
finish();
}
// clipping disabled:
out.lineTo(xe, ye);
this.cx0 = xe;
this.cy0 = ye;
}
private void clip(final int sideCode,
@ -655,22 +709,18 @@ final class TransformingPathConsumer2D {
// add corners to outside stack:
switch (tbCode) {
case MarlinConst.OUTCODE_TOP:
// System.out.println("TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
stack.push(off); // top
return;
case MarlinConst.OUTCODE_BOTTOM:
// System.out.println("BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
stack.push(off + 1); // bottom
return;
default:
// both TOP / BOTTOM:
if ((outcode0 & MarlinConst.OUTCODE_TOP) != 0) {
// System.out.println("TOP + BOTTOM "+ ((off == 0) ? "LEFT" : "RIGHT"));
// top to bottom
stack.push(off); // top
stack.push(off + 1); // bottom
} else {
// System.out.println("BOTTOM + TOP "+ ((off == 0) ? "LEFT" : "RIGHT"));
// bottom to top
stack.push(off + 1); // bottom
stack.push(off); // top
@ -685,34 +735,62 @@ final class TransformingPathConsumer2D {
final float xe, final float ye)
{
final int outcode0 = this.cOutCode;
final int outcode1 = Helpers.outcode(x1, y1, clipRect);
final int outcode2 = Helpers.outcode(x2, y2, clipRect);
final int outcode3 = Helpers.outcode(xe, ye, clipRect);
this.cOutCode = outcode3;
int sideCode = outcode0 & outcode3;
if (sideCode == 0) {
this.gOutCode = 0;
} else {
sideCode &= Helpers.outcode(x1, y1, clipRect);
sideCode &= Helpers.outcode(x2, y2, clipRect);
this.gOutCode &= sideCode;
// Should clip
final int orCode = (outcode0 | outcode1 | outcode2 | outcode3);
if (orCode != 0) {
final int sideCode = outcode0 & outcode1 & outcode2 & outcode3;
// basic rejection criteria:
if (sideCode != 0) {
if (sideCode == 0) {
// ovelap clip:
if (subdivide) {
// avoid reentrance
subdivide = false;
// subdivide curve => callback with subdivided parts:
boolean ret;
if (outside) {
ret = curveSplitter.splitCurve(cox0, coy0, x1, y1,
x2, y2, xe, ye,
orCode, this);
} else {
ret = curveSplitter.splitCurve(cx0, cy0, x1, y1,
x2, y2, xe, ye,
orCode, this);
}
// reentrance is done:
subdivide = true;
if (ret) {
return;
}
}
// already subdivided so render it
} else {
this.cOutCode = outcode3;
this.gOutCode &= sideCode;
// keep last point coordinate before entering the clip again:
this.outside = true;
this.cx0 = xe;
this.cy0 = ye;
this.cox0 = xe;
this.coy0 = ye;
clip(sideCode, outcode0, outcode3);
return;
}
}
this.cOutCode = outcode3;
this.gOutCode = 0;
if (outside) {
finish();
}
// clipping disabled:
out.curveTo(x1, y1, x2, y2, xe, ye);
this.cx0 = xe;
this.cy0 = ye;
}
@Override
@ -720,33 +798,59 @@ final class TransformingPathConsumer2D {
final float xe, final float ye)
{
final int outcode0 = this.cOutCode;
final int outcode1 = Helpers.outcode(x1, y1, clipRect);
final int outcode2 = Helpers.outcode(xe, ye, clipRect);
this.cOutCode = outcode2;
int sideCode = outcode0 & outcode2;
if (sideCode == 0) {
this.gOutCode = 0;
} else {
sideCode &= Helpers.outcode(x1, y1, clipRect);
this.gOutCode &= sideCode;
// Should clip
final int orCode = (outcode0 | outcode1 | outcode2);
if (orCode != 0) {
final int sideCode = outcode0 & outcode1 & outcode2;
// basic rejection criteria:
if (sideCode != 0) {
if (sideCode == 0) {
// ovelap clip:
if (subdivide) {
// avoid reentrance
subdivide = false;
// subdivide curve => callback with subdivided parts:
boolean ret;
if (outside) {
ret = curveSplitter.splitQuad(cox0, coy0, x1, y1,
xe, ye, orCode, this);
} else {
ret = curveSplitter.splitQuad(cx0, cy0, x1, y1,
xe, ye, orCode, this);
}
// reentrance is done:
subdivide = true;
if (ret) {
return;
}
}
// already subdivided so render it
} else {
this.cOutCode = outcode2;
this.gOutCode &= sideCode;
// keep last point coordinate before entering the clip again:
this.outside = true;
this.cx0 = xe;
this.cy0 = ye;
this.cox0 = xe;
this.coy0 = ye;
clip(sideCode, outcode0, outcode2);
return;
}
}
this.cOutCode = outcode2;
this.gOutCode = 0;
if (outside) {
finish();
}
// clipping disabled:
out.quadTo(x1, y1, xe, ye);
this.cx0 = xe;
this.cy0 = ye;
}
@Override
@ -755,6 +859,261 @@ final class TransformingPathConsumer2D {
}
}
static final class CurveClipSplitter {
static final float LEN_TH = MarlinProperties.getSubdividerMinLength();
static final boolean DO_CHECK_LENGTH = (LEN_TH > 0.0f);
private static final boolean TRACE = false;
private static final int MAX_N_CURVES = 3 * 4;
// clip rectangle (ymin, ymax, xmin, xmax):
final float[] clipRect;
// clip rectangle (ymin, ymax, xmin, xmax) including padding:
final float[] clipRectPad = new float[4];
private boolean init_clipRectPad = false;
// This is where the curve to be processed is put. We give it
// enough room to store all curves.
final float[] middle = new float[MAX_N_CURVES * 8 + 2];
// t values at subdivision points
private final float[] subdivTs = new float[MAX_N_CURVES];
// dirty curve
private final Curve curve;
CurveClipSplitter(final RendererContext rdrCtx) {
this.clipRect = rdrCtx.clipRect;
this.curve = rdrCtx.curve;
}
void init() {
this.init_clipRectPad = true;
}
private void initPaddedClip() {
// bounds as half-open intervals: minX <= x < maxX and minY <= y < maxY
// adjust padded clip rectangle (ymin, ymax, xmin, xmax):
// add a rounding error (curve subdivision ~ 0.1px):
final float[] _clipRect = clipRect;
final float[] _clipRectPad = clipRectPad;
_clipRectPad[0] = _clipRect[0] - CLIP_RECT_PADDING;
_clipRectPad[1] = _clipRect[1] + CLIP_RECT_PADDING;
_clipRectPad[2] = _clipRect[2] - CLIP_RECT_PADDING;
_clipRectPad[3] = _clipRect[3] + CLIP_RECT_PADDING;
if (TRACE) {
MarlinUtils.logInfo("clip: X [" + _clipRectPad[2] + " .. " + _clipRectPad[3] +"] "
+ "Y ["+ _clipRectPad[0] + " .. " + _clipRectPad[1] +"]");
}
}
boolean splitLine(final float x0, final float y0,
final float x1, final float y1,
final int outCodeOR,
final PathConsumer2D out)
{
if (TRACE) {
MarlinUtils.logInfo("divLine P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ")");
}
if (DO_CHECK_LENGTH && Helpers.fastLineLen(x0, y0, x1, y1) <= LEN_TH) {
return false;
}
final float[] mid = middle;
mid[0] = x0; mid[1] = y0;
mid[2] = x1; mid[3] = y1;
return subdivideAtIntersections(4, outCodeOR, out);
}
boolean splitQuad(final float x0, final float y0,
final float x1, final float y1,
final float x2, final float y2,
final int outCodeOR,
final PathConsumer2D out)
{
if (TRACE) {
MarlinUtils.logInfo("divQuad P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ")");
}
if (DO_CHECK_LENGTH && Helpers.fastQuadLen(x0, y0, x1, y1, x2, y2) <= LEN_TH) {
return false;
}
final float[] mid = middle;
mid[0] = x0; mid[1] = y0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
return subdivideAtIntersections(6, outCodeOR, out);
}
boolean splitCurve(final float x0, final float y0,
final float x1, final float y1,
final float x2, final float y2,
final float x3, final float y3,
final int outCodeOR,
final PathConsumer2D out)
{
if (TRACE) {
MarlinUtils.logInfo("divCurve P0(" + x0 + ", " + y0 + ") P1(" + x1 + ", " + y1 + ") P2(" + x2 + ", " + y2 + ") P3(" + x3 + ", " + y3 + ")");
}
if (DO_CHECK_LENGTH && Helpers.fastCurvelen(x0, y0, x1, y1, x2, y2, x3, y3) <= LEN_TH) {
return false;
}
final float[] mid = middle;
mid[0] = x0; mid[1] = y0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
mid[6] = x3; mid[7] = y3;
return subdivideAtIntersections(8, outCodeOR, out);
}
private boolean subdivideAtIntersections(final int type, final int outCodeOR,
final PathConsumer2D out)
{
final float[] mid = middle;
final float[] subTs = subdivTs;
if (init_clipRectPad) {
init_clipRectPad = false;
initPaddedClip();
}
final int nSplits = Helpers.findClipPoints(curve, mid, subTs, type,
outCodeOR, clipRectPad);
if (TRACE) {
MarlinUtils.logInfo("nSplits: "+ nSplits);
MarlinUtils.logInfo("subTs: "+Arrays.toString(Arrays.copyOfRange(subTs, 0, nSplits)));
}
if (nSplits == 0) {
// only curve support shortcut
return false;
}
float prevT = 0.0f;
for (int i = 0, off = 0; i < nSplits; i++, off += type) {
final float t = subTs[i];
Helpers.subdivideAt((t - prevT) / (1.0f - prevT),
mid, off, mid, off, type);
prevT = t;
}
for (int i = 0, off = 0; i <= nSplits; i++, off += type) {
if (TRACE) {
MarlinUtils.logInfo("Part Curve "+Arrays.toString(Arrays.copyOfRange(mid, off, off + type)));
}
emitCurrent(type, mid, off, out);
}
return true;
}
static void emitCurrent(final int type, final float[] pts,
final int off, final PathConsumer2D out)
{
// if instead of switch (perf + most probable cases first)
if (type == 8) {
out.curveTo(pts[off + 2], pts[off + 3],
pts[off + 4], pts[off + 5],
pts[off + 6], pts[off + 7]);
} else if (type == 4) {
out.lineTo(pts[off + 2], pts[off + 3]);
} else {
out.quadTo(pts[off + 2], pts[off + 3],
pts[off + 4], pts[off + 5]);
}
}
}
static final class CurveBasicMonotonizer {
private static final int MAX_N_CURVES = 11;
// squared half line width (for stroker)
private float lw2;
// number of splitted curves
int nbSplits;
// This is where the curve to be processed is put. We give it
// enough room to store all curves.
final float[] middle = new float[MAX_N_CURVES * 6 + 2];
// t values at subdivision points
private final float[] subdivTs = new float[MAX_N_CURVES - 1];
// dirty curve
private final Curve curve;
CurveBasicMonotonizer(final RendererContext rdrCtx) {
this.curve = rdrCtx.curve;
}
void init(final float lineWidth) {
this.lw2 = (lineWidth * lineWidth) / 4.0f;
}
CurveBasicMonotonizer curve(final float x0, final float y0,
final float x1, final float y1,
final float x2, final float y2,
final float x3, final float y3)
{
final float[] mid = middle;
mid[0] = x0; mid[1] = y0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
mid[6] = x3; mid[7] = y3;
final float[] subTs = subdivTs;
final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 8, lw2);
float prevT = 0.0f;
for (int i = 0, off = 0; i < nSplits; i++, off += 6) {
final float t = subTs[i];
Helpers.subdivideCubicAt((t - prevT) / (1.0f - prevT),
mid, off, mid, off, off + 6);
prevT = t;
}
this.nbSplits = nSplits;
return this;
}
CurveBasicMonotonizer quad(final float x0, final float y0,
final float x1, final float y1,
final float x2, final float y2)
{
final float[] mid = middle;
mid[0] = x0; mid[1] = y0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
final float[] subTs = subdivTs;
final int nSplits = Helpers.findSubdivPoints(curve, mid, subTs, 6, lw2);
float prevt = 0.0f;
for (int i = 0, off = 0; i < nSplits; i++, off += 4) {
final float t = subTs[i];
Helpers.subdivideQuadAt((t - prevt) / (1.0f - prevt),
mid, off, mid, off, off + 4);
prevt = t;
}
this.nbSplits = nSplits;
return this;
}
}
static final class PathTracer implements PathConsumer2D {
private final String prefix;
private PathConsumer2D out;
@ -808,7 +1167,7 @@ final class TransformingPathConsumer2D {
}
private void log(final String message) {
System.out.println(prefix + message);
MarlinUtils.logInfo(prefix + message);
}
@Override

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2018, 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
@ -27,7 +27,7 @@ package sun.java2d.marlin;
public final class Version {
private static final String VERSION = "marlin-0.8.2-Unsafe-OpenJDK";
private static final String VERSION = "marlin-0.9.1-Unsafe-OpenJDK";
public static String getVersion() {
return VERSION;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2018, 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
@ -42,7 +42,6 @@ public final class Histogram extends StatLong {
for (int i = 2; i < MAX; i++) {
STEPS[i] = STEPS[i - 1] * BUCKET;
}
// System.out.println("Histogram.STEPS = " + Arrays.toString(STEPS));
}
static int bucket(int val) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2016, 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
@ -71,9 +71,7 @@ public class StatLong {
@Override
public String toString() {
final StringBuilder sb = new StringBuilder(128);
toString(sb);
return sb.toString();
return toString(new StringBuilder(128)).toString();
}
public final StringBuilder toString(final StringBuilder sb) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2018, 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
@ -30,7 +30,6 @@ import java.awt.BasicStroke;
import java.awt.geom.PathIterator;
import java.awt.geom.AffineTransform;
import java.security.PrivilegedAction;
import java.security.AccessController;
import sun.security.action.GetPropertyAction;

View File

@ -54,13 +54,24 @@ import javax.imageio.stream.ImageOutputStream;
* @bug 8191814
* @summary Verifies that Marlin rendering generates the same
* images with and without clipping optimization with all possible
* stroke (cap/join) and fill modes (EO rules)
* stroke (cap/join) and/or dashes or fill modes (EO rules)
* for paths made of either 9 lines, 4 quads, 2 cubics (random)
* Note: Use the argument -slow to run more intensive tests (too much time)
* @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine ClipShapeTest
* @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.DMarlinRenderingEngine ClipShapeTest
*/
*
* @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine ClipShapeTest -poly
* @run main/othervm/timeout=240 -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine ClipShapeTest -poly -doDash
* @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine ClipShapeTest -cubic
* @run main/othervm/timeout=240 -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine ClipShapeTest -cubic -doDash
* @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.DMarlinRenderingEngine ClipShapeTest -poly
* @run main/othervm/timeout=240 -Dsun.java2d.renderer=sun.java2d.marlin.DMarlinRenderingEngine ClipShapeTest -poly -doDash
* @run main/othervm/timeout=120 -Dsun.java2d.renderer=sun.java2d.marlin.DMarlinRenderingEngine ClipShapeTest -cubic
* @run main/othervm/timeout=240 -Dsun.java2d.renderer=sun.java2d.marlin.DMarlinRenderingEngine ClipShapeTest -cubic -doDash
*/
public final class ClipShapeTest {
static boolean TX_SCALE = false;
static boolean TX_SHEAR = false;
static final boolean TEST_STROKER = true;
static final boolean TEST_FILLER = true;
@ -73,18 +84,23 @@ public final class ClipShapeTest {
static final int TESTH = 100;
// shape settings:
static final ShapeMode SHAPE_MODE = ShapeMode.NINE_LINE_POLYS;
static ShapeMode SHAPE_MODE = ShapeMode.NINE_LINE_POLYS;
static int THRESHOLD_DELTA;
static long THRESHOLD_NBPIX;
static final boolean SHAPE_REPEAT = true;
// dump path on console:
static final boolean DUMP_SHAPE = true;
static final boolean SHOW_DETAILS = true;
static final boolean SHOW_DETAILS = false; // disabled
static final boolean SHOW_OUTLINE = true;
static final boolean SHOW_POINTS = true;
static final boolean SHOW_INFO = false;
static final int MAX_SHOW_FRAMES = 10;
static final int MAX_SAVE_FRAMES = 100;
// use fixed seed to reproduce always same polygons between tests
static final boolean FIXED_SEED = false;
@ -109,24 +125,13 @@ public final class ClipShapeTest {
static final File OUTPUT_DIR = new File(".");
/**
* Test
* @param args
*/
public static void main(String[] args) {
boolean runSlowTests = (args.length != 0 && "-slow".equals(args[0]));
if (runSlowTests) {
NUM_TESTS = 20000; // or 100000 (very slow)
USE_DASHES = true;
USE_VAR_STROKE = true;
}
static final AtomicBoolean isMarlin = new AtomicBoolean();
static final AtomicBoolean isClipRuntime = new AtomicBoolean();
static {
Locale.setDefault(Locale.US);
// Get Marlin runtime state from its log:
final AtomicBoolean isMarlin = new AtomicBoolean();
final AtomicBoolean isClipRuntime = new AtomicBoolean();
// FIRST: Get Marlin runtime state from its log:
// initialize j.u.l Looger:
final Logger log = Logger.getLogger("sun.java2d.marlin");
@ -171,6 +176,95 @@ public final class ClipShapeTest {
System.setProperty("sun.java2d.renderer.clip", "false");
System.setProperty("sun.java2d.renderer.clip.runtime.enable", "true");
// enable subdivider:
System.setProperty("sun.java2d.renderer.clip.subdivider", "true");
// disable min length check: always subdivide curves at clip edges
System.setProperty("sun.java2d.renderer.clip.subdivider.minLength", "-1");
// If any curve, increase curve accuracy:
// curve length max error:
System.setProperty("sun.java2d.renderer.curve_len_err", "1e-4");
// quad max error:
System.setProperty("sun.java2d.renderer.quad_dec_d2", "5e-4");
// cubic min/max error:
System.setProperty("sun.java2d.renderer.cubic_dec_d2", "1e-3");
System.setProperty("sun.java2d.renderer.cubic_inc_d1", "1e-4"); // or disabled ~ 1e-6
}
/**
* Test
* @param args
*/
public static void main(String[] args) {
boolean runSlowTests = false;
for (String arg : args) {
if ("-slow".equals(arg)) {
System.out.println("slow: enabled.");
runSlowTests = true;
} else if ("-doScale".equals(arg)) {
System.out.println("doScale: enabled.");
TX_SCALE = true;
} else if ("-doShear".equals(arg)) {
System.out.println("doShear: enabled.");
TX_SHEAR = true;
} else if ("-doDash".equals(arg)) {
System.out.println("doDash: enabled.");
USE_DASHES = true;
} else if ("-doVarStroke".equals(arg)) {
System.out.println("doVarStroke: enabled.");
USE_VAR_STROKE = true;
}
// shape mode:
else if (arg.equalsIgnoreCase("-poly")) {
SHAPE_MODE = ShapeMode.NINE_LINE_POLYS;
} else if (arg.equalsIgnoreCase("-bigpoly")) {
SHAPE_MODE = ShapeMode.FIFTY_LINE_POLYS;
} else if (arg.equalsIgnoreCase("-quad")) {
SHAPE_MODE = ShapeMode.FOUR_QUADS;
} else if (arg.equalsIgnoreCase("-cubic")) {
SHAPE_MODE = ShapeMode.TWO_CUBICS;
} else if (arg.equalsIgnoreCase("-mixed")) {
SHAPE_MODE = ShapeMode.MIXED;
}
}
System.out.println("Shape mode: " + SHAPE_MODE);
// adjust image comparison thresholds:
switch(SHAPE_MODE) {
case TWO_CUBICS:
// Define uncertainty for curves:
THRESHOLD_DELTA = 32; // / 256
THRESHOLD_NBPIX = 128; // / 10000
break;
case FOUR_QUADS:
case MIXED:
// Define uncertainty for quads:
// curve subdivision causes curves to be smaller
// then curve offsets are different (more accurate)
THRESHOLD_DELTA = 64; // 64 / 256
THRESHOLD_NBPIX = 256; // 256 / 10000
break;
default:
// Define uncertainty for lines:
// float variant have higher uncertainty
THRESHOLD_DELTA = 8;
THRESHOLD_NBPIX = 8;
}
System.out.println("THRESHOLD_DELTA: "+THRESHOLD_DELTA);
System.out.println("THRESHOLD_NBPIX: "+THRESHOLD_NBPIX);
if (runSlowTests) {
NUM_TESTS = 10000; // or 100000 (very slow)
USE_DASHES = true;
USE_VAR_STROKE = true;
}
System.out.println("ClipShapeTests: image = " + TESTW + " x " + TESTH);
int failures = 0;
@ -179,14 +273,21 @@ public final class ClipShapeTest {
// TODO: test affine transforms ?
if (TEST_STROKER) {
final float[][] dashArrays = (USE_DASHES)
? new float[][]{null, new float[]{1f, 2f}}
final float[][] dashArrays = (USE_DASHES) ?
// small
// new float[][]{new float[]{1f, 2f}}
// normal
new float[][]{new float[]{13f, 7f}}
// large (prime)
// new float[][]{new float[]{41f, 7f}}
// none
: new float[][]{null};
System.out.println("dashes: " + Arrays.toString(dashArrays));
System.out.println("dashes: " + Arrays.deepToString(dashArrays));
final float[] strokeWidths = (USE_VAR_STROKE)
? new float[5] : new float[]{8f};
? new float[5] :
new float[]{10f};
int nsw = 0;
if (USE_VAR_STROKE) {
@ -290,22 +391,20 @@ public final class ClipShapeTest {
final double ratio = (100.0 * testCtx.histPix.count) / testCtx.histAll.count;
System.out.println("Diff ratio: " + testName + " = " + trimTo3Digits(ratio) + " %");
if (false) {
saveImage(diffImage, OUTPUT_DIR, testName + "-diff.png");
}
if (DUMP_SHAPE) {
dumpShape(p2d);
}
if (nd < MAX_SHOW_FRAMES) {
if (SHOW_DETAILS) {
paintShapeDetails(g2dOff, p2d);
paintShapeDetails(g2dOn, p2d);
}
saveImage(imgOff, OUTPUT_DIR, testName + "-off.png");
saveImage(imgOn, OUTPUT_DIR, testName + "-on.png");
saveImage(diffImage, OUTPUT_DIR, testName + "-diff.png");
if (nd < MAX_SAVE_FRAMES) {
if (DUMP_SHAPE) {
dumpShape(p2d);
}
saveImage(imgOff, OUTPUT_DIR, testName + "-off.png");
saveImage(imgOn, OUTPUT_DIR, testName + "-on.png");
saveImage(diffImage, OUTPUT_DIR, testName + "-diff.png");
}
}
}
}
@ -351,6 +450,15 @@ public final class ClipShapeTest {
}
g2d.setColor(Color.GRAY);
// Test scale
if (TX_SCALE) {
g2d.scale(1.2, 1.2);
}
// Test shear
if (TX_SHEAR) {
g2d.shear(0.1, 0.2);
}
return g2d;
}
@ -470,6 +578,8 @@ public final class ClipShapeTest {
}
break;
case PathIterator.SEG_LINETO:
case PathIterator.SEG_QUADTO:
case PathIterator.SEG_CUBICTO:
if (SHOW_POINTS) {
g2d.setColor((nLine % 2 == 0) ? COLOR_LINETO_ODD : COLOR_LINETO_EVEN);
}
@ -515,6 +625,12 @@ public final class ClipShapeTest {
case PathIterator.SEG_LINETO:
System.out.println("p2d.lineTo(" + coords[0] + ", " + coords[1] + ");");
break;
case PathIterator.SEG_QUADTO:
System.out.println("p2d.quadTo(" + coords[0] + ", " + coords[1] + ", " + coords[2] + ", " + coords[3] + ");");
break;
case PathIterator.SEG_CUBICTO:
System.out.println("p2d.curveTo(" + coords[0] + ", " + coords[1] + ", " + coords[2] + ", " + coords[3] + ", " + coords[4] + ", " + coords[5] + ");");
break;
case PathIterator.SEG_CLOSE:
System.out.println("p2d.closePath();");
break;
@ -580,10 +696,54 @@ public final class ClipShapeTest {
@Override
public String toString() {
if (isStroke()) {
return "TestSetup{id=" + id + ", shapeMode=" + shapeMode + ", closed=" + closed
+ ", strokeWidth=" + strokeWidth + ", strokeCap=" + getCap(strokeCap) + ", strokeJoin=" + getJoin(strokeJoin)
+ ((dashes != null) ? ", dashes: " + Arrays.toString(dashes) : "")
+ '}';
}
return "TestSetup{id=" + id + ", shapeMode=" + shapeMode + ", closed=" + closed
+ ", strokeWidth=" + strokeWidth + ", strokeCap=" + strokeCap + ", strokeJoin=" + strokeJoin
+ ((dashes != null) ? ", dashes: " + Arrays.toString(dashes) : "")
+ ", windingRule=" + windingRule + '}';
+ ", fill"
+ ", windingRule=" + getWindingRule(windingRule) + '}';
}
private static String getCap(final int cap) {
switch (cap) {
case BasicStroke.CAP_BUTT:
return "CAP_BUTT";
case BasicStroke.CAP_ROUND:
return "CAP_ROUND";
case BasicStroke.CAP_SQUARE:
return "CAP_SQUARE";
default:
return "";
}
}
private static String getJoin(final int join) {
switch (join) {
case BasicStroke.JOIN_MITER:
return "JOIN_MITER";
case BasicStroke.JOIN_ROUND:
return "JOIN_ROUND";
case BasicStroke.JOIN_BEVEL:
return "JOIN_BEVEL";
default:
return "";
}
}
private static String getWindingRule(final int rule) {
switch (rule) {
case PathIterator.WIND_EVEN_ODD:
return "WIND_EVEN_ODD";
case PathIterator.WIND_NON_ZERO:
return "WIND_NON_ZERO";
default:
return "";
}
}
}
@ -618,16 +778,23 @@ public final class ClipShapeTest {
// max difference on grayscale values:
v = (int) Math.ceil(Math.abs(dg / 3.0));
aDifPix[i] = toInt(v, v, v);
// TODO: count warnings
if (v <= THRESHOLD_DELTA) {
aDifPix[i] = 0;
} else {
aDifPix[i] = toInt(v, v, v);
localCtx.add(v);
localCtx.add(v);
}
globalCtx.add(v);
}
if (!localCtx.isDiff()) {
if (!localCtx.isDiff() || (localCtx.histPix.count <= THRESHOLD_NBPIX)) {
return null;
}
localCtx.dump();
return diffImage;
}