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:
parent
7efc35390e
commit
385ad9e160
@ -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,
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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 = "
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 = "
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user