提交 bbb70c58 编写于 作者: L lbourges

8144718: Pisces / Marlin Strokers may generate invalid curves with huge coordinates and round joins

Reviewed-by: flar, prr
上级 32f3be23
......@@ -87,6 +87,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
private int joinStyle;
private float lineWidth2;
private float invHalfLineWidth2Sq;
private final float[] offset0 = new float[2];
private final float[] offset1 = new float[2];
......@@ -158,6 +159,7 @@ final class Stroker implements PathConsumer2D, MarlinConst {
this.out = pc2d;
this.lineWidth2 = lineWidth / 2f;
this.invHalfLineWidth2Sq = 1f / (2f * lineWidth2 * lineWidth2);
this.capStyle = capStyle;
this.joinStyle = joinStyle;
......@@ -252,11 +254,11 @@ final class Stroker implements PathConsumer2D, MarlinConst {
// The sign of the dot product of mx,my and omx,omy is equal to the
// the sign of the cosine of ext
// (ext is the angle between omx,omy and mx,my).
double cosext = omx * mx + omy * my;
final float cosext = omx * mx + omy * my;
// If it is >=0, we know that abs(ext) is <= 90 degrees, so we only
// need 1 curve to approximate the circle section that joins omx,omy
// and mx,my.
final int numCurves = cosext >= 0 ? 1 : 2;
final int numCurves = (cosext >= 0f) ? 1 : 2;
switch (numCurves) {
case 1:
......@@ -302,14 +304,22 @@ final class Stroker implements PathConsumer2D, MarlinConst {
final float mx, final float my,
boolean rev)
{
float cosext2 = (omx * mx + omy * my) / (2f * lineWidth2 * lineWidth2);
final float cosext2 = (omx * mx + omy * my) * invHalfLineWidth2Sq;
// check round off errors producing cos(ext) > 1 and a NaN below
// cos(ext) == 1 implies colinear segments and an empty join anyway
if (cosext2 >= 0.5f) {
// just return to avoid generating a flat curve:
return;
}
// cv is the length of P1-P0 and P2-P3 divided by the radius of the arc
// (so, cv assumes the arc has radius 1). P0, P1, P2, P3 are the points that
// define the bezier curve we're computing.
// It is computed using the constraints that P1-P0 and P3-P2 are parallel
// to the arc tangents at the endpoints, and that |P1-P0|=|P3-P2|.
float cv = (float) ((4.0 / 3.0) * sqrt(0.5-cosext2) /
(1.0 + sqrt(cosext2+0.5)));
float cv = (float) ((4.0 / 3.0) * sqrt(0.5 - cosext2) /
(1.0 + sqrt(cosext2 + 0.5)));
// if clockwise, we need to negate cv.
if (rev) { // rev is equivalent to isCW(omx, omy, mx, my)
cv = -cv;
......
/*
* Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2015, 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
......@@ -193,11 +193,11 @@ final class Stroker implements PathConsumer2D {
// The sign of the dot product of mx,my and omx,omy is equal to the
// the sign of the cosine of ext
// (ext is the angle between omx,omy and mx,my).
double cosext = omx * mx + omy * my;
final float cosext = omx * mx + omy * my;
// If it is >=0, we know that abs(ext) is <= 90 degrees, so we only
// need 1 curve to approximate the circle section that joins omx,omy
// and mx,my.
final int numCurves = cosext >= 0 ? 1 : 2;
final int numCurves = (cosext >= 0f) ? 1 : 2;
switch (numCurves) {
case 1:
......@@ -242,14 +242,22 @@ final class Stroker implements PathConsumer2D {
final float mx, final float my,
boolean rev)
{
float cosext2 = (omx * mx + omy * my) / (2 * lineWidth2 * lineWidth2);
final float cosext2 = (omx * mx + omy * my) / (2f * lineWidth2 * lineWidth2);
// check round off errors producing cos(ext) > 1 and a NaN below
// cos(ext) == 1 implies colinear segments and an empty join anyway
if (cosext2 >= 0.5f) {
// just return to avoid generating a flat curve:
return;
}
// cv is the length of P1-P0 and P2-P3 divided by the radius of the arc
// (so, cv assumes the arc has radius 1). P0, P1, P2, P3 are the points that
// define the bezier curve we're computing.
// It is computed using the constraints that P1-P0 and P3-P2 are parallel
// to the arc tangents at the endpoints, and that |P1-P0|=|P3-P2|.
float cv = (float) ((4.0 / 3.0) * sqrt(0.5-cosext2) /
(1.0 + sqrt(cosext2+0.5)));
float cv = (float) ((4.0 / 3.0) * sqrt(0.5 - cosext2) /
(1.0 + sqrt(cosext2 + 0.5)));
// if clockwise, we need to negate cv.
if (rev) { // rev is equivalent to isCW(omx, omy, mx, my)
cv = -cv;
......
/*
* Copyright (c) 2015, 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.
*
* 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.
*/
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import static java.awt.geom.PathIterator.SEG_CLOSE;
import static java.awt.geom.PathIterator.SEG_CUBICTO;
import static java.awt.geom.PathIterator.SEG_LINETO;
import static java.awt.geom.PathIterator.SEG_MOVETO;
import static java.awt.geom.PathIterator.SEG_QUADTO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
/**
* @test @bug 8144718
* @summary Check the Stroker.drawBezApproxForArc() bug (stoke with round
* joins): if cosext2 > 0.5, it generates curves with NaN coordinates
* @run main TextClipErrorTest
*/
public class TextClipErrorTest {
static final boolean SAVE_IMAGE = false;
static final boolean SERIALIZE = false;
public static void main(String[] args) {
Locale.setDefault(Locale.US);
// initialize j.u.l Looger:
final Logger log = Logger.getLogger("sun.java2d.marlin");
log.addHandler(new Handler() {
@Override
public void publish(LogRecord record) {
Throwable th = record.getThrown();
// detect potential Throwable thrown by XxxArrayCache.check():
if (th != null && th.getClass() == Throwable.class) {
StackTraceElement[] stackElements = th.getStackTrace();
for (int i = 0; i < stackElements.length; i++) {
StackTraceElement e = stackElements[i];
if (e.getClassName().startsWith("sun.java2d.marlin")
&& e.getClassName().contains("ArrayCache")
&& "check".equals(e.getMethodName()))
{
System.out.println("Test failed:\n"
+ record.getMessage());
th.printStackTrace(System.out);
throw new RuntimeException("Test failed: ", th);
}
}
}
}
@Override
public void flush() {
}
@Override
public void close() throws SecurityException {
}
});
log.info("TextClipErrorTest: start");
// enable Marlin logging & internal checks:
System.setProperty("sun.java2d.renderer.log", "true");
System.setProperty("sun.java2d.renderer.useLogger", "true");
System.setProperty("sun.java2d.renderer.doChecks", "true");
BufferedImage image = new BufferedImage(256, 256,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = image.createGraphics();
g2d.setColor(Color.red);
try {
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Font font = g2d.getFont();
FontRenderContext frc = new FontRenderContext(
new AffineTransform(), true, true);
g2d.setStroke(new BasicStroke(4.0f,
BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND));
final Shape badShape;
if (SERIALIZE) {
final GlyphVector gv1 = font.createGlyphVector(frc, "\u00d6");
final Shape textShape = gv1.getOutline();
final AffineTransform at1 = AffineTransform.getTranslateInstance(
-2091202.554154681, 5548.601436981691);
badShape = at1.createTransformedShape(textShape);
serializeShape(badShape);
} else {
badShape = deserializeShape();
}
g2d.draw(badShape);
// Draw anything within bounds and it fails:
g2d.draw(new Line2D.Double(10, 20, 30, 40));
if (SAVE_IMAGE) {
final File file = new File("TextClipErrorTest.png");
System.out.println("Writing file: " + file.getAbsolutePath());
ImageIO.write(image, "PNG", file);
}
} catch (IOException ex) {
ex.printStackTrace();
} finally {
g2d.dispose();
log.info("TextClipErrorTest: end");
}
}
private static void serializeShape(Shape shape) {
final double[] coords = new double[6];
final int len = 32;
final ArrayList<Integer> typeList = new ArrayList<Integer>(len);
final ArrayList<double[]> coordsList = new ArrayList<double[]>(len);
for (PathIterator pi = shape.getPathIterator(null);
!pi.isDone(); pi.next())
{
switch (pi.currentSegment(coords)) {
case SEG_MOVETO:
typeList.add(SEG_MOVETO);
coordsList.add(Arrays.copyOf(coords, 2));
break;
case SEG_LINETO:
typeList.add(SEG_LINETO);
coordsList.add(Arrays.copyOf(coords, 2));
break;
case SEG_QUADTO:
typeList.add(SEG_QUADTO);
coordsList.add(Arrays.copyOf(coords, 4));
break;
case SEG_CUBICTO:
typeList.add(SEG_CUBICTO);
coordsList.add(Arrays.copyOf(coords, 6));
break;
case SEG_CLOSE:
typeList.add(SEG_CLOSE);
coordsList.add(null);
break;
default:
}
}
final StringBuilder sb = new StringBuilder(1024);
// types:
sb.append("private static final int[] SHAPE_TYPES = new int[]{\n");
for (Integer i : typeList) {
sb.append(i).append(",\n");
}
sb.append("};\n");
// coords:
sb.append("private static final double[][] SHAPE_COORDS = new double[][]{\n");
for (double[] c : coordsList) {
if (c == null) {
sb.append("null,\n");
} else {
sb.append("new double[]{");
for (int i = 0; i < c.length; i++) {
sb.append(c[i]).append(",");
}
sb.append("},\n");
}
}
sb.append("};\n");
System.out.println("Shape size: " + typeList.size());
System.out.println("Serialized shape:\n" + sb.toString());
}
private static Shape deserializeShape() {
final Path2D.Double path = new Path2D.Double();
for (int i = 0; i < SHAPE_TYPES.length; i++) {
double[] coords = SHAPE_COORDS[i];
switch (SHAPE_TYPES[i]) {
case SEG_MOVETO:
path.moveTo(coords[0], coords[1]);
break;
case SEG_LINETO:
path.lineTo(coords[0], coords[1]);
break;
case SEG_QUADTO:
path.quadTo(coords[0], coords[1],
coords[2], coords[3]);
break;
case SEG_CUBICTO:
path.curveTo(coords[0], coords[1],
coords[2], coords[3],
coords[4], coords[5]);
break;
case SEG_CLOSE:
path.closePath();
break;
default:
}
}
return path;
}
// generated code:
private static final int[] SHAPE_TYPES = new int[]{
0,
2,
2,
2,
2,
2,
2,
2,
2,
4,
0,
2,
2,
2,
2,
2,
2,
2,
2,
4,
0,
1,
1,
1,
1,
4,
0,
1,
1,
1,
1,
4,
};
private static final double[][] SHAPE_COORDS = new double[][]{
new double[]{-2091197.819779681, 5540.648311981691,},
new double[]{-2091199.116654681, 5540.648311981691, -2091199.874467181, 5541.609249481691,},
new double[]{-2091200.632279681, 5542.570186981691, -2091200.632279681, 5544.242061981691,},
new double[]{-2091200.632279681, 5545.882686981691, -2091199.874467181, 5546.843624481691,},
new double[]{-2091199.116654681, 5547.804561981691, -2091197.819779681, 5547.804561981691,},
new double[]{-2091196.538529681, 5547.804561981691, -2091195.780717181, 5546.843624481691,},
new double[]{-2091195.022904681, 5545.882686981691, -2091195.022904681, 5544.242061981691,},
new double[]{-2091195.022904681, 5542.570186981691, -2091195.780717181, 5541.609249481691,},
new double[]{-2091196.538529681, 5540.648311981691, -2091197.819779681, 5540.648311981691,},
null,
new double[]{-2091197.819779681, 5539.695186981691,},
new double[]{-2091195.991654681, 5539.695186981691, -2091194.890092181, 5540.929561981691,},
new double[]{-2091193.788529681, 5542.163936981691, -2091193.788529681, 5544.242061981691,},
new double[]{-2091193.788529681, 5546.304561981691, -2091194.890092181, 5547.538936981691,},
new double[]{-2091195.991654681, 5548.773311981691, -2091197.819779681, 5548.773311981691,},
new double[]{-2091199.663529681, 5548.773311981691, -2091200.772904681, 5547.538936981691,},
new double[]{-2091201.882279681, 5546.304561981691, -2091201.882279681, 5544.242061981691,},
new double[]{-2091201.882279681, 5542.163936981691, -2091200.772904681, 5540.929561981691,},
new double[]{-2091199.663529681, 5539.695186981691, -2091197.819779681, 5539.695186981691,},
null,
new double[]{-2091197.210404681, 5537.835811981691,},
new double[]{-2091196.022904681, 5537.835811981691,},
new double[]{-2091196.022904681, 5539.023311981691,},
new double[]{-2091197.210404681, 5539.023311981691,},
new double[]{-2091197.210404681, 5537.835811981691,},
null,
new double[]{-2091199.632279681, 5537.835811981691,},
new double[]{-2091198.444779681, 5537.835811981691,},
new double[]{-2091198.444779681, 5539.023311981691,},
new double[]{-2091199.632279681, 5539.023311981691,},
new double[]{-2091199.632279681, 5537.835811981691,},
null,
};
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册