1 /* 2 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.java2d.pisces; 27 28 import java.awt.Shape; 29 import java.awt.BasicStroke; 30 import java.awt.geom.NoninvertibleTransformException; 31 import java.awt.geom.Path2D; 32 import java.awt.geom.AffineTransform; 33 import java.awt.geom.PathIterator; 34 35 import sun.awt.geom.PathConsumer2D; 36 import sun.java2d.pipe.Region; 37 import sun.java2d.pipe.RenderingEngine; 38 import sun.java2d.pipe.AATileGenerator; 39 40 public class PiscesRenderingEngine extends RenderingEngine { 41 private static enum NormMode {OFF, ON_NO_AA, ON_WITH_AA} 42 43 /** 44 * Create a widened path as specified by the parameters. 45 * <p> 46 * The specified {@code src} {@link Shape} is widened according 47 * to the specified attribute parameters as per the 48 * {@link BasicStroke} specification. 49 * 50 * @param src the source path to be widened 51 * @param width the width of the widened path as per {@code BasicStroke} 52 * @param caps the end cap decorations as per {@code BasicStroke} 53 * @param join the segment join decorations as per {@code BasicStroke} 54 * @param miterlimit the miter limit as per {@code BasicStroke} 55 * @param dashes the dash length array as per {@code BasicStroke} 56 * @param dashphase the initial dash phase as per {@code BasicStroke} 57 * @return the widened path stored in a new {@code Shape} object 58 * @since 1.7 59 */ 60 public Shape createStrokedShape(Shape src, 61 float width, 62 int caps, 63 int join, 64 float miterlimit, 65 float dashes[], 66 float dashphase) 67 { 68 final Path2D p2d = new Path2D.Float(); 69 70 strokeTo(src, 71 null, 72 width, 73 NormMode.OFF, 74 caps, 75 join, 76 miterlimit, 77 dashes, 78 dashphase, 79 new PathConsumer2D() { 80 public void moveTo(float x0, float y0) { 81 p2d.moveTo(x0, y0); 82 } 83 public void lineTo(float x1, float y1) { 84 p2d.lineTo(x1, y1); 85 } 86 public void closePath() { 87 p2d.closePath(); 88 } 89 public void pathDone() {} 90 public void curveTo(float x1, float y1, 91 float x2, float y2, 92 float x3, float y3) { 93 p2d.curveTo(x1, y1, x2, y2, x3, y3); 94 } 95 public void quadTo(float x1, float y1, float x2, float y2) { 96 p2d.quadTo(x1, y1, x2, y2); 97 } 98 public long getNativeConsumer() { 99 throw new InternalError("Not using a native peer"); 100 } 101 }); 102 return p2d; 103 } 104 105 /** 106 * Sends the geometry for a widened path as specified by the parameters 107 * to the specified consumer. 108 * <p> 109 * The specified {@code src} {@link Shape} is widened according 110 * to the parameters specified by the {@link BasicStroke} object. 111 * Adjustments are made to the path as appropriate for the 112 * {@link VALUE_STROKE_NORMALIZE} hint if the {@code normalize} 113 * boolean parameter is true. 114 * Adjustments are made to the path as appropriate for the 115 * {@link VALUE_ANTIALIAS_ON} hint if the {@code antialias} 116 * boolean parameter is true. 117 * <p> 118 * The geometry of the widened path is forwarded to the indicated 119 * {@link PathConsumer2D} object as it is calculated. 120 * 121 * @param src the source path to be widened 122 * @param bs the {@code BasicSroke} object specifying the 123 * decorations to be applied to the widened path 124 * @param normalize indicates whether stroke normalization should 125 * be applied 126 * @param antialias indicates whether or not adjustments appropriate 127 * to antialiased rendering should be applied 128 * @param consumer the {@code PathConsumer2D} instance to forward 129 * the widened geometry to 130 * @since 1.7 131 */ 132 public void strokeTo(Shape src, 133 AffineTransform at, 134 BasicStroke bs, 135 boolean thin, 136 boolean normalize, 137 boolean antialias, 138 final PathConsumer2D consumer) 139 { 140 NormMode norm = (normalize) ? 141 ((antialias) ? NormMode.ON_WITH_AA : NormMode.ON_NO_AA) 142 : NormMode.OFF; 143 strokeTo(src, at, bs, thin, norm, antialias, consumer); 144 } 145 146 void strokeTo(Shape src, 147 AffineTransform at, 148 BasicStroke bs, 149 boolean thin, 150 NormMode normalize, 151 boolean antialias, 152 PathConsumer2D pc2d) 153 { 154 float lw; 155 if (thin) { 156 if (antialias) { 157 lw = userSpaceLineWidth(at, 0.5f); 158 } else { 159 lw = userSpaceLineWidth(at, 1.0f); 160 } 161 } else { 162 lw = bs.getLineWidth(); 163 } 164 strokeTo(src, 165 at, 166 lw, 167 normalize, 168 bs.getEndCap(), 169 bs.getLineJoin(), 170 bs.getMiterLimit(), 171 bs.getDashArray(), 172 bs.getDashPhase(), 173 pc2d); 174 } 175 176 private float userSpaceLineWidth(AffineTransform at, float lw) { 177 178 double widthScale; 179 180 if ((at.getType() & (AffineTransform.TYPE_GENERAL_TRANSFORM | 181 AffineTransform.TYPE_GENERAL_SCALE)) != 0) { 182 widthScale = Math.sqrt(at.getDeterminant()); 183 } else { 184 /* First calculate the "maximum scale" of this transform. */ 185 double A = at.getScaleX(); // m00 186 double C = at.getShearX(); // m01 187 double B = at.getShearY(); // m10 188 double D = at.getScaleY(); // m11 189 190 /* 191 * Given a 2 x 2 affine matrix [ A B ] such that 192 * [ C D ] 193 * v' = [x' y'] = [Ax + Cy, Bx + Dy], we want to 194 * find the maximum magnitude (norm) of the vector v' 195 * with the constraint (x^2 + y^2 = 1). 196 * The equation to maximize is 197 * |v'| = sqrt((Ax+Cy)^2+(Bx+Dy)^2) 198 * or |v'| = sqrt((AA+BB)x^2 + 2(AC+BD)xy + (CC+DD)y^2). 199 * Since sqrt is monotonic we can maximize |v'|^2 200 * instead and plug in the substitution y = sqrt(1 - x^2). 201 * Trigonometric equalities can then be used to get 202 * rid of most of the sqrt terms. 203 */ 204 205 double EA = A*A + B*B; // x^2 coefficient 206 double EB = 2*(A*C + B*D); // xy coefficient 207 double EC = C*C + D*D; // y^2 coefficient 208 209 /* 210 * There is a lot of calculus omitted here. 211 * 212 * Conceptually, in the interests of understanding the 213 * terms that the calculus produced we can consider 214 * that EA and EC end up providing the lengths along 215 * the major axes and the hypot term ends up being an 216 * adjustment for the additional length along the off-axis 217 * angle of rotated or sheared ellipses as well as an 218 * adjustment for the fact that the equation below 219 * averages the two major axis lengths. (Notice that 220 * the hypot term contains a part which resolves to the 221 * difference of these two axis lengths in the absence 222 * of rotation.) 223 * 224 * In the calculus, the ratio of the EB and (EA-EC) terms 225 * ends up being the tangent of 2*theta where theta is 226 * the angle that the long axis of the ellipse makes 227 * with the horizontal axis. Thus, this equation is 228 * calculating the length of the hypotenuse of a triangle 229 * along that axis. 230 */ 231 232 double hypot = Math.sqrt(EB*EB + (EA-EC)*(EA-EC)); 233 /* sqrt omitted, compare to squared limits below. */ 234 double widthsquared = ((EA + EC + hypot)/2.0); 235 236 widthScale = Math.sqrt(widthsquared); 237 } 238 239 return (float) (lw / widthScale); 240 } 241 242 void strokeTo(Shape src, 243 AffineTransform at, 244 float width, 245 NormMode normalize, 246 int caps, 247 int join, 248 float miterlimit, 249 float dashes[], 250 float dashphase, 251 PathConsumer2D pc2d) 252 { 253 // We use inat and outat so that in Stroker and Dasher we can work only 254 // with the pre-transformation coordinates. This will repeat a lot of 255 // computations done in the path iterator, but the alternative is to 256 // work with transformed paths and compute untransformed coordinates 257 // as needed. This would be faster but I do not think the complexity 258 // of working with both untransformed and transformed coordinates in 259 // the same code is worth it. 260 // However, if a path's width is constant after a transformation, 261 // we can skip all this untransforming. 262 263 // If normalization is off we save some transformations by not 264 // transforming the input to pisces. Instead, we apply the 265 // transformation after the path processing has been done. 266 // We can't do this if normalization is on, because it isn't a good 267 // idea to normalize before the transformation is applied. 268 AffineTransform inat = null; 269 AffineTransform outat = null; 270 271 PathIterator pi = null; 272 273 if (at != null && !at.isIdentity()) { 274 final double a = at.getScaleX(); 275 final double b = at.getShearX(); 276 final double c = at.getShearY(); 277 final double d = at.getScaleY(); 278 final double det = a * d - c * b; 279 if (Math.abs(det) <= 2 * Float.MIN_VALUE) { 280 // this rendering engine takes one dimensional curves and turns 281 // them into 2D shapes by giving them width. 282 // However, if everything is to be passed through a singular 283 // transformation, these 2D shapes will be squashed down to 1D 284 // again so, nothing can be drawn. 285 286 // Every path needs an initial moveTo and a pathDone. If these 287 // aren't there this causes a SIGSEV in libawt.so (at the time 288 // of writing of this comment (September 16, 2010)). Actually, 289 // I'm not sure if the moveTo is necessary to avoid the SIGSEV 290 // but the pathDone is definitely needed. 291 pc2d.moveTo(0, 0); 292 pc2d.pathDone(); 293 return; 294 } 295 296 // If the transform is a constant multiple of an orthogonal transformation 297 // then every length is just multiplied by a constant, so we just 298 // need to transform input paths to stroker and tell stroker 299 // the scaled width. This condition is satisfied if 300 // a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we 301 // leave a bit of room for error. 302 if (nearZero(a*b + c*d, 2) && nearZero(a*a+c*c - (b*b+d*d), 2)) { 303 double scale = Math.sqrt(a*a + c*c); 304 if (dashes != null) { 305 dashes = java.util.Arrays.copyOf(dashes, dashes.length); 306 for (int i = 0; i < dashes.length; i++) { 307 dashes[i] = (float)(scale * dashes[i]); 308 } 309 dashphase = (float)(scale * dashphase); 310 } 311 width = (float)(scale * width); 312 pi = src.getPathIterator(at); 313 if (normalize != NormMode.OFF) { 314 pi = new NormalizingPathIterator(pi, normalize); 315 } 316 // leave inat and outat null. 317 } else { 318 // We only need the inverse if normalization is on. Otherwise 319 // we just don't transform the input paths, do all the stroking 320 // and then transform out output (instead of making PathIterator 321 // apply the transformation, us applying the inverse, and then 322 // us applying the transform again to our output). 323 outat = at; 324 if (normalize != NormMode.OFF) { 325 try { 326 inat = outat.createInverse(); 327 } catch (NoninvertibleTransformException e) { 328 // we made sure this can't happen 329 e.printStackTrace(); 330 } 331 pi = src.getPathIterator(at); 332 pi = new NormalizingPathIterator(pi, normalize); 333 } else { 334 pi = src.getPathIterator(null); 335 } 336 } 337 } else { 338 // either at is null or it's the identity. In either case 339 // we don't transform the path. 340 pi = src.getPathIterator(null); 341 if (normalize != NormMode.OFF) { 342 pi = new NormalizingPathIterator(pi, normalize); 343 } 344 } 345 346 pc2d = TransformingPathConsumer2D.transformConsumer(pc2d, outat); 347 pc2d = new Stroker(pc2d, width, caps, join, miterlimit); 348 if (dashes != null) { 349 pc2d = new Dasher(pc2d, dashes, dashphase); 350 } 351 pc2d = TransformingPathConsumer2D.transformConsumer(pc2d, inat); 352 353 pathTo(pi, pc2d); 354 } 355 356 private static boolean nearZero(double num, int nulps) { 357 return Math.abs(num) < nulps * Math.ulp(num); 358 } 359 360 private static class NormalizingPathIterator implements PathIterator { 361 362 private final PathIterator src; 363 364 // the adjustment applied to the current position. 365 private float curx_adjust, cury_adjust; 366 // the adjustment applied to the last moveTo position. 367 private float movx_adjust, movy_adjust; 368 369 // constants used in normalization computations 370 private final float lval, rval; 371 372 NormalizingPathIterator(PathIterator src, NormMode mode) { 373 this.src = src; 374 switch (mode) { 375 case ON_NO_AA: 376 // round to nearest (0.25, 0.25) pixel 377 lval = rval = 0.25f; 378 break; 379 case ON_WITH_AA: 380 // round to nearest pixel center 381 lval = 0f; 382 rval = 0.5f; 383 break; 384 case OFF: 385 throw new InternalError("A NormalizingPathIterator should " + 386 "not be created if no normalization is being done"); 387 default: 388 throw new InternalError("Unrecognized normalization mode"); 389 } 390 } 391 392 public int currentSegment(float[] coords) { 393 int type = src.currentSegment(coords); 394 395 int lastCoord; 396 switch(type) { 397 case PathIterator.SEG_CUBICTO: 398 lastCoord = 4; 399 break; 400 case PathIterator.SEG_QUADTO: 401 lastCoord = 2; 402 break; 403 case PathIterator.SEG_LINETO: 404 case PathIterator.SEG_MOVETO: 405 lastCoord = 0; 406 break; 407 case PathIterator.SEG_CLOSE: 408 // we don't want to deal with this case later. We just exit now 409 curx_adjust = movx_adjust; 410 cury_adjust = movy_adjust; 411 return type; 412 default: 413 throw new InternalError("Unrecognized curve type"); 414 } 415 416 // normalize endpoint 417 float x_adjust = (float)Math.floor(coords[lastCoord] + lval) + 418 rval - coords[lastCoord]; 419 float y_adjust = (float)Math.floor(coords[lastCoord+1] + lval) + 420 rval - coords[lastCoord + 1]; 421 422 coords[lastCoord ] += x_adjust; 423 coords[lastCoord + 1] += y_adjust; 424 425 // now that the end points are done, normalize the control points 426 switch(type) { 427 case PathIterator.SEG_CUBICTO: 428 coords[0] += curx_adjust; 429 coords[1] += cury_adjust; 430 coords[2] += x_adjust; 431 coords[3] += y_adjust; 432 break; 433 case PathIterator.SEG_QUADTO: 434 coords[0] += (curx_adjust + x_adjust) / 2; 435 coords[1] += (cury_adjust + y_adjust) / 2; 436 break; 437 case PathIterator.SEG_LINETO: 438 break; 439 case PathIterator.SEG_MOVETO: 440 movx_adjust = x_adjust; 441 movy_adjust = y_adjust; 442 break; 443 case PathIterator.SEG_CLOSE: 444 throw new InternalError("This should be handled earlier."); 445 } 446 curx_adjust = x_adjust; 447 cury_adjust = y_adjust; 448 return type; 449 } 450 451 public int currentSegment(double[] coords) { 452 float[] tmp = new float[6]; 453 int type = this.currentSegment(tmp); 454 for (int i = 0; i < 6; i++) { 455 coords[i] = (float) tmp[i]; 456 } 457 return type; 458 } 459 460 public int getWindingRule() { 461 return src.getWindingRule(); 462 } 463 464 public boolean isDone() { 465 return src.isDone(); 466 } 467 468 public void next() { 469 src.next(); 470 } 471 } 472 473 static void pathTo(PathIterator pi, PathConsumer2D pc2d) { 474 RenderingEngine.feedConsumer(pi, pc2d); 475 pc2d.pathDone(); 476 } 477 478 /** 479 * Construct an antialiased tile generator for the given shape with 480 * the given rendering attributes and store the bounds of the tile 481 * iteration in the bbox parameter. 482 * The {@code at} parameter specifies a transform that should affect 483 * both the shape and the {@code BasicStroke} attributes. 484 * The {@code clip} parameter specifies the current clip in effect 485 * in device coordinates and can be used to prune the data for the 486 * operation, but the renderer is not required to perform any 487 * clipping. 488 * If the {@code BasicStroke} parameter is null then the shape 489 * should be filled as is, otherwise the attributes of the 490 * {@code BasicStroke} should be used to specify a draw operation. 491 * The {@code thin} parameter indicates whether or not the 492 * transformed {@code BasicStroke} represents coordinates smaller 493 * than the minimum resolution of the antialiasing rasterizer as 494 * specified by the {@code getMinimumAAPenWidth()} method. 495 * <p> 496 * Upon returning, this method will fill the {@code bbox} parameter 497 * with 4 values indicating the bounds of the iteration of the 498 * tile generator. 499 * The iteration order of the tiles will be as specified by the 500 * pseudo-code: 501 * <pre> 502 * for (y = bbox[1]; y < bbox[3]; y += tileheight) { 503 * for (x = bbox[0]; x < bbox[2]; x += tilewidth) { 504 * } 505 * } 506 * </pre> 507 * If there is no output to be rendered, this method may return 508 * null. 509 * 510 * @param s the shape to be rendered (fill or draw) 511 * @param at the transform to be applied to the shape and the 512 * stroke attributes 513 * @param clip the current clip in effect in device coordinates 514 * @param bs if non-null, a {@code BasicStroke} whose attributes 515 * should be applied to this operation 516 * @param thin true if the transformed stroke attributes are smaller 517 * than the minimum dropout pen width 518 * @param normalize true if the {@code VALUE_STROKE_NORMALIZE} 519 * {@code RenderingHint} is in effect 520 * @param bbox returns the bounds of the iteration 521 * @return the {@code AATileGenerator} instance to be consulted 522 * for tile coverages, or null if there is no output to render 523 * @since 1.7 524 */ 525 public AATileGenerator getAATileGenerator(Shape s, 526 AffineTransform at, 527 Region clip, 528 BasicStroke bs, 529 boolean thin, 530 boolean normalize, 531 int bbox[]) 532 { 533 Renderer r; 534 NormMode norm = (normalize) ? NormMode.ON_WITH_AA : NormMode.OFF; 535 if (bs == null) { 536 PathIterator pi; 537 if (normalize) { 538 pi = new NormalizingPathIterator(s.getPathIterator(at), norm); 539 } else { 540 pi = s.getPathIterator(at); 541 } 542 r = new Renderer(3, 3, 543 clip.getLoX(), clip.getLoY(), 544 clip.getWidth(), clip.getHeight(), 545 pi.getWindingRule()); 546 pathTo(pi, r); 547 } else { 548 r = new Renderer(3, 3, 549 clip.getLoX(), clip.getLoY(), 550 clip.getWidth(), clip.getHeight(), 551 PathIterator.WIND_NON_ZERO); 552 strokeTo(s, at, bs, thin, norm, true, r); 553 } 554 r.endRendering(); 555 PiscesTileGenerator ptg = new PiscesTileGenerator(r, r.MAX_AA_ALPHA); 556 ptg.getBbox(bbox); 557 return ptg; 558 } 559 560 /** 561 * Returns the minimum pen width that the antialiasing rasterizer 562 * can represent without dropouts occuring. 563 * @since 1.7 564 */ 565 public float getMinimumAAPenSize() { 566 return 0.5f; 567 } 568 569 static { 570 if (PathIterator.WIND_NON_ZERO != Renderer.WIND_NON_ZERO || 571 PathIterator.WIND_EVEN_ODD != Renderer.WIND_EVEN_ODD || 572 BasicStroke.JOIN_MITER != Stroker.JOIN_MITER || 573 BasicStroke.JOIN_ROUND != Stroker.JOIN_ROUND || 574 BasicStroke.JOIN_BEVEL != Stroker.JOIN_BEVEL || 575 BasicStroke.CAP_BUTT != Stroker.CAP_BUTT || 576 BasicStroke.CAP_ROUND != Stroker.CAP_ROUND || 577 BasicStroke.CAP_SQUARE != Stroker.CAP_SQUARE) 578 { 579 throw new InternalError("mismatched renderer constants"); 580 } 581 } 582 } 583