Path: blob/master/src/java.desktop/share/classes/sun/java2d/marlin/DMarlinRenderingEngine.java
41159 views
/*1* Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.java2d.marlin;2627import java.awt.BasicStroke;28import java.awt.Shape;29import java.awt.geom.AffineTransform;30import java.awt.geom.Path2D;31import java.awt.geom.PathIterator;32import java.security.AccessController;33import java.util.Arrays;34import sun.awt.geom.PathConsumer2D;35import static sun.java2d.marlin.MarlinUtils.logInfo;36import sun.java2d.ReentrantContextProvider;37import sun.java2d.ReentrantContextProviderCLQ;38import sun.java2d.ReentrantContextProviderTL;39import sun.java2d.pipe.AATileGenerator;40import sun.java2d.pipe.Region;41import sun.java2d.pipe.RenderingEngine;42import sun.security.action.GetPropertyAction;4344/**45* Marlin RendererEngine implementation (derived from Pisces)46*/47public final class DMarlinRenderingEngine extends RenderingEngine48implements MarlinConst49{50// slightly slower ~2% if enabled stroker clipping (lines) but skipping cap / join handling is few percents faster in specific cases51static final boolean DISABLE_2ND_STROKER_CLIPPING = true;5253static final boolean DO_TRACE_PATH = false;5455static final boolean DO_CLIP = MarlinProperties.isDoClip();56static final boolean DO_CLIP_FILL = true;57static final boolean DO_CLIP_RUNTIME_ENABLE = MarlinProperties.isDoClipRuntimeFlag();5859private static final float MIN_PEN_SIZE = 1.0f / MIN_SUBPIXELS;6061static final double UPPER_BND = Float.MAX_VALUE / 2.0d;62static final double LOWER_BND = -UPPER_BND;6364private enum NormMode {65ON_WITH_AA {66@Override67PathIterator getNormalizingPathIterator(final RendererContext rdrCtx,68final PathIterator src)69{70// NormalizingPathIterator NearestPixelCenter:71return rdrCtx.nPCPathIterator.init(src);72}73},74ON_NO_AA{75@Override76PathIterator getNormalizingPathIterator(final RendererContext rdrCtx,77final PathIterator src)78{79// NearestPixel NormalizingPathIterator:80return rdrCtx.nPQPathIterator.init(src);81}82},83OFF{84@Override85PathIterator getNormalizingPathIterator(final RendererContext rdrCtx,86final PathIterator src)87{88// return original path iterator if normalization is disabled:89return src;90}91};9293abstract PathIterator getNormalizingPathIterator(RendererContext rdrCtx,94PathIterator src);95}9697/**98* Public constructor99*/100public DMarlinRenderingEngine() {101super();102logSettings(DMarlinRenderingEngine.class.getName());103}104105/**106* Create a widened path as specified by the parameters.107* <p>108* The specified {@code src} {@link Shape} is widened according109* to the specified attribute parameters as per the110* {@link BasicStroke} specification.111*112* @param src the source path to be widened113* @param width the width of the widened path as per {@code BasicStroke}114* @param caps the end cap decorations as per {@code BasicStroke}115* @param join the segment join decorations as per {@code BasicStroke}116* @param miterlimit the miter limit as per {@code BasicStroke}117* @param dashes the dash length array as per {@code BasicStroke}118* @param dashphase the initial dash phase as per {@code BasicStroke}119* @return the widened path stored in a new {@code Shape} object120* @since 1.7121*/122@Override123public Shape createStrokedShape(Shape src,124float width,125int caps,126int join,127float miterlimit,128float[] dashes,129float dashphase)130{131final RendererContext rdrCtx = getRendererContext();132try {133// initialize a large copyable Path2D to avoid a lot of array growing:134final Path2D.Double p2d = rdrCtx.getPath2D();135136strokeTo(rdrCtx,137src,138null,139width,140NormMode.OFF,141caps,142join,143miterlimit,144dashes,145dashphase,146rdrCtx.transformerPC2D.wrapPath2D(p2d)147);148149// Use Path2D copy constructor (trim)150return new Path2D.Double(p2d);151152} finally {153// recycle the RendererContext instance154returnRendererContext(rdrCtx);155}156}157158/**159* Sends the geometry for a widened path as specified by the parameters160* to the specified consumer.161* <p>162* The specified {@code src} {@link Shape} is widened according163* to the parameters specified by the {@link BasicStroke} object.164* Adjustments are made to the path as appropriate for the165* {@link java.awt.RenderingHints#VALUE_STROKE_NORMALIZE} hint if the166* {@code normalize} boolean parameter is true.167* Adjustments are made to the path as appropriate for the168* {@link java.awt.RenderingHints#VALUE_ANTIALIAS_ON} hint if the169* {@code antialias} boolean parameter is true.170* <p>171* The geometry of the widened path is forwarded to the indicated172* {@link PathConsumer2D} object as it is calculated.173*174* @param src the source path to be widened175* @param at the transform to be applied to the shape and the176* stroke attributes177* @param bs the {@code BasicStroke} object specifying the178* decorations to be applied to the widened path179* @param thin true if the transformed stroke attributes are smaller180* than the minimum dropout pen width181* @param normalize indicates whether stroke normalization should182* be applied183* @param antialias indicates whether or not adjustments appropriate184* to antialiased rendering should be applied185* @param consumer the {@code PathConsumer2D} instance to forward186* the widened geometry to187* @since 1.7188*/189@Override190public void strokeTo(Shape src,191AffineTransform at,192BasicStroke bs,193boolean thin,194boolean normalize,195boolean antialias,196final PathConsumer2D consumer)197{198strokeTo(src, at, null, bs, thin, normalize, antialias, consumer);199}200201/**202* Sends the geometry for a widened path as specified by the parameters203* to the specified consumer.204* <p>205* The specified {@code src} {@link Shape} is widened according206* to the parameters specified by the {@link BasicStroke} object.207* Adjustments are made to the path as appropriate for the208* {@link java.awt.RenderingHints#VALUE_STROKE_NORMALIZE} hint if the209* {@code normalize} boolean parameter is true.210* Adjustments are made to the path as appropriate for the211* {@link java.awt.RenderingHints#VALUE_ANTIALIAS_ON} hint if the212* {@code antialias} boolean parameter is true.213* <p>214* The geometry of the widened path is forwarded to the indicated215* {@link PathConsumer2D} object as it is calculated.216*217* @param src the source path to be widened218* @param at the transform to be applied to the shape and the219* stroke attributes220* @param clip the current clip in effect in device coordinates221* @param bs the {@code BasicStroke} object specifying the222* decorations to be applied to the widened path223* @param thin true if the transformed stroke attributes are smaller224* than the minimum dropout pen width225* @param normalize indicates whether stroke normalization should226* be applied227* @param antialias indicates whether or not adjustments appropriate228* to antialiased rendering should be applied229* @param consumer the {@code PathConsumer2D} instance to forward230* the widened geometry to231* @since 17232*/233/* @Override (only for 17+) */234public void strokeTo(Shape src,235AffineTransform at,236Region clip,237BasicStroke bs,238boolean thin,239boolean normalize,240boolean antialias,241final PathConsumer2D consumer)242{243// Test if at is identity:244final AffineTransform _at = (at != null && !at.isIdentity()) ? at245: null;246247final NormMode norm = (normalize) ?248((antialias) ? NormMode.ON_WITH_AA : NormMode.ON_NO_AA)249: NormMode.OFF;250251final RendererContext rdrCtx = getRendererContext();252try {253if ((clip != null) &&254(DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime()))) {255// Define the initial clip bounds:256final double[] clipRect = rdrCtx.clipRect;257258// Adjust the clipping rectangle with the renderer offsets259final double rdrOffX = 0.25d; // LBO: is it correct for AA or non AA cases ?260final double rdrOffY = 0.25d; // see NearestPixelQuarter (depends on normalization ?)261262// add a small rounding error:263final double margin = 1e-3d;264265clipRect[0] = clip.getLoY()266- margin + rdrOffY;267clipRect[1] = clip.getLoY() + clip.getHeight()268+ margin + rdrOffY;269clipRect[2] = clip.getLoX()270- margin + rdrOffX;271clipRect[3] = clip.getLoX() + clip.getWidth()272+ margin + rdrOffX;273274if (MarlinConst.DO_LOG_CLIP) {275MarlinUtils.logInfo("clipRect (clip): "276+ Arrays.toString(rdrCtx.clipRect));277}278279// Enable clipping:280rdrCtx.doClip = true;281}282283strokeTo(rdrCtx, src, _at, bs, thin, norm, antialias,284rdrCtx.p2dAdapter.init(consumer));285} finally {286// recycle the RendererContext instance287returnRendererContext(rdrCtx);288}289}290291void strokeTo(final RendererContext rdrCtx,292Shape src,293AffineTransform at,294BasicStroke bs,295boolean thin,296NormMode normalize,297boolean antialias,298DPathConsumer2D pc2d)299{300double lw;301if (thin) {302if (antialias) {303lw = userSpaceLineWidth(at, MIN_PEN_SIZE);304} else {305lw = userSpaceLineWidth(at, 1.0d);306}307} else {308lw = bs.getLineWidth();309}310strokeTo(rdrCtx,311src,312at,313lw,314normalize,315bs.getEndCap(),316bs.getLineJoin(),317bs.getMiterLimit(),318bs.getDashArray(),319bs.getDashPhase(),320pc2d);321}322323private double userSpaceLineWidth(AffineTransform at, double lw) {324325double widthScale;326327if (at == null) {328widthScale = 1.0d;329} else if ((at.getType() & (AffineTransform.TYPE_GENERAL_TRANSFORM |330AffineTransform.TYPE_GENERAL_SCALE)) != 0) {331// Determinant may be negative (flip), use its absolute value:332widthScale = Math.sqrt(Math.abs(at.getDeterminant()));333} else {334// First calculate the "maximum scale" of this transform.335double A = at.getScaleX(); // m00336double C = at.getShearX(); // m01337double B = at.getShearY(); // m10338double D = at.getScaleY(); // m11339340/*341* Given a 2 x 2 affine matrix [ A B ] such that342* [ C D ]343* v' = [x' y'] = [Ax + Cy, Bx + Dy], we want to344* find the maximum magnitude (norm) of the vector v'345* with the constraint (x^2 + y^2 = 1).346* The equation to maximize is347* |v'| = sqrt((Ax+Cy)^2+(Bx+Dy)^2)348* or |v'| = sqrt((AA+BB)x^2 + 2(AC+BD)xy + (CC+DD)y^2).349* Since sqrt is monotonic we can maximize |v'|^2350* instead and plug in the substitution y = sqrt(1 - x^2).351* Trigonometric equalities can then be used to get352* rid of most of the sqrt terms.353*/354355double EA = A*A + B*B; // x^2 coefficient356double EB = 2.0d * (A*C + B*D); // xy coefficient357double EC = C*C + D*D; // y^2 coefficient358359/*360* There is a lot of calculus omitted here.361*362* Conceptually, in the interests of understanding the363* terms that the calculus produced we can consider364* that EA and EC end up providing the lengths along365* the major axes and the hypot term ends up being an366* adjustment for the additional length along the off-axis367* angle of rotated or sheared ellipses as well as an368* adjustment for the fact that the equation below369* averages the two major axis lengths. (Notice that370* the hypot term contains a part which resolves to the371* difference of these two axis lengths in the absence372* of rotation.)373*374* In the calculus, the ratio of the EB and (EA-EC) terms375* ends up being the tangent of 2*theta where theta is376* the angle that the long axis of the ellipse makes377* with the horizontal axis. Thus, this equation is378* calculating the length of the hypotenuse of a triangle379* along that axis.380*/381382double hypot = Math.sqrt(EB*EB + (EA-EC)*(EA-EC));383// sqrt omitted, compare to squared limits below.384double widthsquared = ((EA + EC + hypot) / 2.0d);385386widthScale = Math.sqrt(widthsquared);387}388389return (lw / widthScale);390}391392void strokeTo(final RendererContext rdrCtx,393Shape src,394AffineTransform at,395double width,396NormMode norm,397int caps,398int join,399float miterlimit,400float[] dashes,401float dashphase,402DPathConsumer2D pc2d)403{404// We use strokerat so that in Stroker and Dasher we can work only405// with the pre-transformation coordinates. This will repeat a lot of406// computations done in the path iterator, but the alternative is to407// work with transformed paths and compute untransformed coordinates408// as needed. This would be faster but I do not think the complexity409// of working with both untransformed and transformed coordinates in410// the same code is worth it.411// However, if a path's width is constant after a transformation,412// we can skip all this untransforming.413414// As pathTo() will check transformed coordinates for invalid values415// (NaN / Infinity) to ignore such points, it is necessary to apply the416// transformation before the path processing.417AffineTransform strokerat = null;418419int dashLen = -1;420boolean recycleDashes = false;421double[] dashesD = null;422423// Ensure converting dashes to double precision:424if (dashes != null) {425recycleDashes = true;426dashLen = dashes.length;427dashesD = rdrCtx.dasher.copyDashArray(dashes);428}429430if (at != null && !at.isIdentity()) {431final double a = at.getScaleX();432final double b = at.getShearX();433final double c = at.getShearY();434final double d = at.getScaleY();435final double det = a * d - c * b;436437if (Math.abs(det) <= (2.0d * Double.MIN_VALUE)) {438// this rendering engine takes one dimensional curves and turns439// them into 2D shapes by giving them width.440// However, if everything is to be passed through a singular441// transformation, these 2D shapes will be squashed down to 1D442// again so, nothing can be drawn.443444// Every path needs an initial moveTo and a pathDone. If these445// are not there this causes a SIGSEGV in libawt.so (at the time446// of writing of this comment (September 16, 2010)). Actually,447// I am not sure if the moveTo is necessary to avoid the SIGSEGV448// but the pathDone is definitely needed.449pc2d.moveTo(0.0d, 0.0d);450pc2d.pathDone();451return;452}453454// If the transform is a constant multiple of an orthogonal transformation455// then every length is just multiplied by a constant, so we just456// need to transform input paths to stroker and tell stroker457// the scaled width. This condition is satisfied if458// a*b == -c*d && a*a+c*c == b*b+d*d. In the actual check below, we459// leave a bit of room for error.460if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) {461final double scale = Math.sqrt(a*a + c*c);462463if (dashesD != null) {464for (int i = 0; i < dashLen; i++) {465dashesD[i] *= scale;466}467dashphase *= scale;468}469width *= scale;470471// by now strokerat == null. Input paths to472// stroker (and maybe dasher) will have the full transform at473// applied to them and nothing will happen to the output paths.474} else {475strokerat = at;476477// by now strokerat == at. Input paths to478// stroker (and maybe dasher) will have the full transform at479// applied to them, then they will be normalized, and then480// the inverse of *only the non translation part of at* will481// be applied to the normalized paths. This won't cause problems482// in stroker, because, suppose at = T*A, where T is just the483// translation part of at, and A is the rest. T*A has already484// been applied to Stroker/Dasher's input. Then Ainv will be485// applied. Ainv*T*A is not equal to T, but it is a translation,486// which means that none of stroker's assumptions about its487// input will be violated. After all this, A will be applied488// to stroker's output.489}490} else {491// either at is null or it's the identity. In either case492// we don't transform the path.493at = null;494}495496final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;497498if (DO_TRACE_PATH) {499// trace Stroker:500pc2d = transformerPC2D.traceStroker(pc2d);501}502503if (USE_SIMPLIFIER) {504// Use simplifier after stroker before Renderer505// to remove collinear segments (notably due to cap square)506pc2d = rdrCtx.simplifier.init(pc2d);507}508509// deltaTransformConsumer may adjust the clip rectangle:510pc2d = transformerPC2D.deltaTransformConsumer(pc2d, strokerat);511512// stroker will adjust the clip rectangle (width / miter limit):513pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit,514(dashesD == null));515516// Curve Monotizer:517rdrCtx.monotonizer.init(width);518519if (dashesD != null) {520if (DO_TRACE_PATH) {521pc2d = transformerPC2D.traceDasher(pc2d);522}523pc2d = rdrCtx.dasher.init(pc2d, dashesD, dashLen, dashphase,524recycleDashes);525526if (DISABLE_2ND_STROKER_CLIPPING) {527// disable stoker clipping:528rdrCtx.stroker.disableClipping();529}530531} else if (rdrCtx.doClip && (caps != CAP_BUTT)) {532if (DO_TRACE_PATH) {533pc2d = transformerPC2D.traceClosedPathDetector(pc2d);534}535536// If no dash and clip is enabled:537// detect closedPaths (polygons) for caps538pc2d = transformerPC2D.detectClosedPath(pc2d);539}540pc2d = transformerPC2D.inverseDeltaTransformConsumer(pc2d, strokerat);541542if (DO_TRACE_PATH) {543// trace Input:544pc2d = transformerPC2D.traceInput(pc2d);545}546547final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx,548src.getPathIterator(at));549550pathTo(rdrCtx, pi, pc2d);551552/*553* Pipeline seems to be:554* shape.getPathIterator(at)555* -> (NormalizingPathIterator)556* -> (inverseDeltaTransformConsumer)557* -> (Dasher)558* -> Stroker559* -> (deltaTransformConsumer)560*561* -> (CollinearSimplifier) to remove redundant segments562*563* -> pc2d = Renderer (bounding box)564*/565}566567private static boolean nearZero(final double num) {568return Math.abs(num) < 2.0d * Math.ulp(num);569}570571abstract static class NormalizingPathIterator implements PathIterator {572573private PathIterator src;574575// the adjustment applied to the current position.576private double curx_adjust, cury_adjust;577// the adjustment applied to the last moveTo position.578private double movx_adjust, movy_adjust;579580private final double[] tmp;581582NormalizingPathIterator(final double[] tmp) {583this.tmp = tmp;584}585586final NormalizingPathIterator init(final PathIterator src) {587this.src = src;588return this; // fluent API589}590591/**592* Disposes this path iterator:593* clean up before reusing this instance594*/595final void dispose() {596// free source PathIterator:597this.src = null;598}599600@Override601public final int currentSegment(final double[] coords) {602int lastCoord;603final int type = src.currentSegment(coords);604605switch(type) {606case PathIterator.SEG_MOVETO:607case PathIterator.SEG_LINETO:608lastCoord = 0;609break;610case PathIterator.SEG_QUADTO:611lastCoord = 2;612break;613case PathIterator.SEG_CUBICTO:614lastCoord = 4;615break;616case PathIterator.SEG_CLOSE:617// we don't want to deal with this case later. We just exit now618curx_adjust = movx_adjust;619cury_adjust = movy_adjust;620return type;621default:622throw new InternalError("Unrecognized curve type");623}624625// normalize endpoint626double coord, x_adjust, y_adjust;627628coord = coords[lastCoord];629x_adjust = normCoord(coord); // new coord630coords[lastCoord] = x_adjust;631x_adjust -= coord;632633coord = coords[lastCoord + 1];634y_adjust = normCoord(coord); // new coord635coords[lastCoord + 1] = y_adjust;636y_adjust -= coord;637638// now that the end points are done, normalize the control points639switch(type) {640case PathIterator.SEG_MOVETO:641movx_adjust = x_adjust;642movy_adjust = y_adjust;643break;644case PathIterator.SEG_LINETO:645break;646case PathIterator.SEG_QUADTO:647coords[0] += (curx_adjust + x_adjust) / 2.0d;648coords[1] += (cury_adjust + y_adjust) / 2.0d;649break;650case PathIterator.SEG_CUBICTO:651coords[0] += curx_adjust;652coords[1] += cury_adjust;653coords[2] += x_adjust;654coords[3] += y_adjust;655break;656case PathIterator.SEG_CLOSE:657// handled earlier658default:659}660curx_adjust = x_adjust;661cury_adjust = y_adjust;662return type;663}664665abstract double normCoord(final double coord);666667@Override668public final int currentSegment(final float[] coords) {669final double[] _tmp = tmp; // dirty670int type = this.currentSegment(_tmp);671for (int i = 0; i < 6; i++) {672coords[i] = (float)_tmp[i];673}674return type;675}676677@Override678public final int getWindingRule() {679return src.getWindingRule();680}681682@Override683public final boolean isDone() {684if (src.isDone()) {685// Dispose this instance:686dispose();687return true;688}689return false;690}691692@Override693public final void next() {694src.next();695}696697static final class NearestPixelCenter698extends NormalizingPathIterator699{700NearestPixelCenter(final double[] tmp) {701super(tmp);702}703704@Override705double normCoord(final double coord) {706// round to nearest pixel center707return Math.floor(coord) + 0.5d;708}709}710711static final class NearestPixelQuarter712extends NormalizingPathIterator713{714NearestPixelQuarter(final double[] tmp) {715super(tmp);716}717718@Override719double normCoord(final double coord) {720// round to nearest (0.25, 0.25) pixel quarter721return Math.floor(coord + 0.25d) + 0.25d;722}723}724}725726private static void pathTo(final RendererContext rdrCtx, final PathIterator pi,727DPathConsumer2D pc2d)728{729if (USE_PATH_SIMPLIFIER) {730// Use path simplifier at the first step731// to remove useless points732pc2d = rdrCtx.pathSimplifier.init(pc2d);733}734735// mark context as DIRTY:736rdrCtx.dirty = true;737738pathToLoop(rdrCtx.double6, pi, pc2d);739740// mark context as CLEAN:741rdrCtx.dirty = false;742}743744private static void pathToLoop(final double[] coords, final PathIterator pi,745final DPathConsumer2D pc2d)746{747// ported from DuctusRenderingEngine.feedConsumer() but simplified:748// - removed skip flag = !subpathStarted749// - removed pathClosed (ie subpathStarted not set to false)750boolean subpathStarted = false;751752for (; !pi.isDone(); pi.next()) {753switch (pi.currentSegment(coords)) {754case PathIterator.SEG_MOVETO:755/* Checking SEG_MOVETO coordinates if they are out of the756* [LOWER_BND, UPPER_BND] range. This check also handles NaN757* and Infinity values. Skipping next path segment in case of758* invalid data.759*/760if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&761coords[1] < UPPER_BND && coords[1] > LOWER_BND)762{763pc2d.moveTo(coords[0], coords[1]);764subpathStarted = true;765}766break;767case PathIterator.SEG_LINETO:768/* Checking SEG_LINETO coordinates if they are out of the769* [LOWER_BND, UPPER_BND] range. This check also handles NaN770* and Infinity values. Ignoring current path segment in case771* of invalid data. If segment is skipped its endpoint772* (if valid) is used to begin new subpath.773*/774if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&775coords[1] < UPPER_BND && coords[1] > LOWER_BND)776{777if (subpathStarted) {778pc2d.lineTo(coords[0], coords[1]);779} else {780pc2d.moveTo(coords[0], coords[1]);781subpathStarted = true;782}783}784break;785case PathIterator.SEG_QUADTO:786// Quadratic curves take two points787/* Checking SEG_QUADTO coordinates if they are out of the788* [LOWER_BND, UPPER_BND] range. This check also handles NaN789* and Infinity values. Ignoring current path segment in case790* of invalid endpoints's data. Equivalent to the SEG_LINETO791* if endpoint coordinates are valid but there are invalid data792* among other coordinates793*/794if (coords[2] < UPPER_BND && coords[2] > LOWER_BND &&795coords[3] < UPPER_BND && coords[3] > LOWER_BND)796{797if (subpathStarted) {798if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&799coords[1] < UPPER_BND && coords[1] > LOWER_BND)800{801pc2d.quadTo(coords[0], coords[1],802coords[2], coords[3]);803} else {804pc2d.lineTo(coords[2], coords[3]);805}806} else {807pc2d.moveTo(coords[2], coords[3]);808subpathStarted = true;809}810}811break;812case PathIterator.SEG_CUBICTO:813// Cubic curves take three points814/* Checking SEG_CUBICTO coordinates if they are out of the815* [LOWER_BND, UPPER_BND] range. This check also handles NaN816* and Infinity values. Ignoring current path segment in case817* of invalid endpoints's data. Equivalent to the SEG_LINETO818* if endpoint coordinates are valid but there are invalid data819* among other coordinates820*/821if (coords[4] < UPPER_BND && coords[4] > LOWER_BND &&822coords[5] < UPPER_BND && coords[5] > LOWER_BND)823{824if (subpathStarted) {825if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&826coords[1] < UPPER_BND && coords[1] > LOWER_BND &&827coords[2] < UPPER_BND && coords[2] > LOWER_BND &&828coords[3] < UPPER_BND && coords[3] > LOWER_BND)829{830pc2d.curveTo(coords[0], coords[1],831coords[2], coords[3],832coords[4], coords[5]);833} else {834pc2d.lineTo(coords[4], coords[5]);835}836} else {837pc2d.moveTo(coords[4], coords[5]);838subpathStarted = true;839}840}841break;842case PathIterator.SEG_CLOSE:843if (subpathStarted) {844pc2d.closePath();845// do not set subpathStarted to false846// in case of missing moveTo() after close()847}848break;849default:850}851}852pc2d.pathDone();853}854855/**856* Construct an antialiased tile generator for the given shape with857* the given rendering attributes and store the bounds of the tile858* iteration in the bbox parameter.859* The {@code at} parameter specifies a transform that should affect860* both the shape and the {@code BasicStroke} attributes.861* The {@code clip} parameter specifies the current clip in effect862* in device coordinates and can be used to prune the data for the863* operation, but the renderer is not required to perform any864* clipping.865* If the {@code BasicStroke} parameter is null then the shape866* should be filled as is, otherwise the attributes of the867* {@code BasicStroke} should be used to specify a draw operation.868* The {@code thin} parameter indicates whether or not the869* transformed {@code BasicStroke} represents coordinates smaller870* than the minimum resolution of the antialiasing rasterizer as871* specified by the {@code getMinimumAAPenWidth()} method.872* <p>873* Upon returning, this method will fill the {@code bbox} parameter874* with 4 values indicating the bounds of the iteration of the875* tile generator.876* The iteration order of the tiles will be as specified by the877* pseudo-code:878* <pre>879* for (y = bbox[1]; y < bbox[3]; y += tileheight) {880* for (x = bbox[0]; x < bbox[2]; x += tilewidth) {881* }882* }883* </pre>884* If there is no output to be rendered, this method may return885* null.886*887* @param s the shape to be rendered (fill or draw)888* @param at the transform to be applied to the shape and the889* stroke attributes890* @param clip the current clip in effect in device coordinates891* @param bs if non-null, a {@code BasicStroke} whose attributes892* should be applied to this operation893* @param thin true if the transformed stroke attributes are smaller894* than the minimum dropout pen width895* @param normalize true if the {@code VALUE_STROKE_NORMALIZE}896* {@code RenderingHint} is in effect897* @param bbox returns the bounds of the iteration898* @return the {@code AATileGenerator} instance to be consulted899* for tile coverages, or null if there is no output to render900* @since 1.7901*/902@Override903public AATileGenerator getAATileGenerator(Shape s,904AffineTransform at,905Region clip,906BasicStroke bs,907boolean thin,908boolean normalize,909int[] bbox)910{911MarlinTileGenerator ptg = null;912Renderer r = null;913914final RendererContext rdrCtx = getRendererContext();915try {916if (DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime())) {917// Define the initial clip bounds:918final double[] clipRect = rdrCtx.clipRect;919920// Adjust the clipping rectangle with the renderer offsets921final double rdrOffX = Renderer.RDR_OFFSET_X;922final double rdrOffY = Renderer.RDR_OFFSET_Y;923924// add a small rounding error:925final double margin = 1e-3d;926927clipRect[0] = clip.getLoY()928- margin + rdrOffY;929clipRect[1] = clip.getLoY() + clip.getHeight()930+ margin + rdrOffY;931clipRect[2] = clip.getLoX()932- margin + rdrOffX;933clipRect[3] = clip.getLoX() + clip.getWidth()934+ margin + rdrOffX;935936if (MarlinConst.DO_LOG_CLIP) {937MarlinUtils.logInfo("clipRect (clip): "938+ Arrays.toString(rdrCtx.clipRect));939}940941// Enable clipping:942rdrCtx.doClip = true;943}944945// Test if at is identity:946final AffineTransform _at = (at != null && !at.isIdentity()) ? at947: null;948949final NormMode norm = (normalize) ? NormMode.ON_WITH_AA : NormMode.OFF;950951if (bs == null) {952// fill shape:953final PathIterator pi = norm.getNormalizingPathIterator(rdrCtx,954s.getPathIterator(_at));955956// note: Winding rule may be EvenOdd ONLY for fill operations !957r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),958clip.getWidth(), clip.getHeight(),959pi.getWindingRule());960961DPathConsumer2D pc2d = r;962963if (DO_CLIP_FILL && rdrCtx.doClip) {964if (DO_TRACE_PATH) {965// trace Filler:966pc2d = rdrCtx.transformerPC2D.traceFiller(pc2d);967}968pc2d = rdrCtx.transformerPC2D.pathClipper(pc2d);969}970971if (DO_TRACE_PATH) {972// trace Input:973pc2d = rdrCtx.transformerPC2D.traceInput(pc2d);974}975pathTo(rdrCtx, pi, pc2d);976977} else {978// draw shape with given stroke:979r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),980clip.getWidth(), clip.getHeight(),981WIND_NON_ZERO);982983strokeTo(rdrCtx, s, _at, bs, thin, norm, true, r);984}985if (r.endRendering()) {986ptg = rdrCtx.ptg.init();987ptg.getBbox(bbox);988// note: do not returnRendererContext(rdrCtx)989// as it will be called later by MarlinTileGenerator.dispose()990r = null;991}992} finally {993if (r != null) {994// dispose renderer and recycle the RendererContext instance:995r.dispose();996}997}998999// Return null to cancel AA tile generation (nothing to render)1000return ptg;1001}10021003@Override1004public AATileGenerator getAATileGenerator(double x, double y,1005double dx1, double dy1,1006double dx2, double dy2,1007double lw1, double lw2,1008Region clip,1009int[] bbox)1010{1011// REMIND: Deal with large coordinates!1012double ldx1, ldy1, ldx2, ldy2;1013boolean innerpgram = (lw1 > 0.0d && lw2 > 0.0d);10141015if (innerpgram) {1016ldx1 = dx1 * lw1;1017ldy1 = dy1 * lw1;1018ldx2 = dx2 * lw2;1019ldy2 = dy2 * lw2;1020x -= (ldx1 + ldx2) / 2.0d;1021y -= (ldy1 + ldy2) / 2.0d;1022dx1 += ldx1;1023dy1 += ldy1;1024dx2 += ldx2;1025dy2 += ldy2;1026if (lw1 > 1.0d && lw2 > 1.0d) {1027// Inner parallelogram was entirely consumed by stroke...1028innerpgram = false;1029}1030} else {1031ldx1 = ldy1 = ldx2 = ldy2 = 0.0d;1032}10331034MarlinTileGenerator ptg = null;1035Renderer r = null;10361037final RendererContext rdrCtx = getRendererContext();1038try {1039r = rdrCtx.renderer.init(clip.getLoX(), clip.getLoY(),1040clip.getWidth(), clip.getHeight(),1041WIND_EVEN_ODD);10421043r.moveTo( x, y);1044r.lineTo( (x+dx1), (y+dy1));1045r.lineTo( (x+dx1+dx2), (y+dy1+dy2));1046r.lineTo( (x+dx2), (y+dy2));1047r.closePath();10481049if (innerpgram) {1050x += ldx1 + ldx2;1051y += ldy1 + ldy2;1052dx1 -= 2.0d * ldx1;1053dy1 -= 2.0d * ldy1;1054dx2 -= 2.0d * ldx2;1055dy2 -= 2.0d * ldy2;1056r.moveTo( x, y);1057r.lineTo( (x+dx1), (y+dy1));1058r.lineTo( (x+dx1+dx2), (y+dy1+dy2));1059r.lineTo( (x+dx2), (y+dy2));1060r.closePath();1061}1062r.pathDone();10631064if (r.endRendering()) {1065ptg = rdrCtx.ptg.init();1066ptg.getBbox(bbox);1067// note: do not returnRendererContext(rdrCtx)1068// as it will be called later by MarlinTileGenerator.dispose()1069r = null;1070}1071} finally {1072if (r != null) {1073// dispose renderer and recycle the RendererContext instance:1074r.dispose();1075}1076}10771078// Return null to cancel AA tile generation (nothing to render)1079return ptg;1080}10811082/**1083* Returns the minimum pen width that the antialiasing rasterizer1084* can represent without dropouts occuring.1085* @since 1.71086*/1087@Override1088public float getMinimumAAPenSize() {1089return MIN_PEN_SIZE;1090}10911092static {1093if (PathIterator.WIND_NON_ZERO != WIND_NON_ZERO ||1094PathIterator.WIND_EVEN_ODD != WIND_EVEN_ODD ||1095BasicStroke.JOIN_MITER != JOIN_MITER ||1096BasicStroke.JOIN_ROUND != JOIN_ROUND ||1097BasicStroke.JOIN_BEVEL != JOIN_BEVEL ||1098BasicStroke.CAP_BUTT != CAP_BUTT ||1099BasicStroke.CAP_ROUND != CAP_ROUND ||1100BasicStroke.CAP_SQUARE != CAP_SQUARE)1101{1102throw new InternalError("mismatched renderer constants");1103}1104}11051106// --- RendererContext handling ---1107// use ThreadLocal or ConcurrentLinkedQueue to get one RendererContext1108private static final boolean USE_THREAD_LOCAL;11091110// reference type stored in either TL or CLQ1111static final int REF_TYPE;11121113// Per-thread RendererContext1114private static final ReentrantContextProvider<RendererContext> RDR_CTX_PROVIDER;11151116// Static initializer to use TL or CLQ mode1117static {1118USE_THREAD_LOCAL = MarlinProperties.isUseThreadLocal();11191120// Soft reference by default:1121@SuppressWarnings("removal")1122final String refType = AccessController.doPrivileged(1123new GetPropertyAction("sun.java2d.renderer.useRef",1124"soft"));1125switch (refType) {1126default:1127case "soft":1128REF_TYPE = ReentrantContextProvider.REF_SOFT;1129break;1130case "weak":1131REF_TYPE = ReentrantContextProvider.REF_WEAK;1132break;1133case "hard":1134REF_TYPE = ReentrantContextProvider.REF_HARD;1135break;1136}11371138if (USE_THREAD_LOCAL) {1139RDR_CTX_PROVIDER = new ReentrantContextProviderTL<RendererContext>(REF_TYPE)1140{1141@Override1142protected RendererContext newContext() {1143return RendererContext.createContext();1144}1145};1146} else {1147RDR_CTX_PROVIDER = new ReentrantContextProviderCLQ<RendererContext>(REF_TYPE)1148{1149@Override1150protected RendererContext newContext() {1151return RendererContext.createContext();1152}1153};1154}1155}11561157private static boolean SETTINGS_LOGGED = !ENABLE_LOGS;11581159private static void logSettings(final String reClass) {1160// log information at startup1161if (SETTINGS_LOGGED) {1162return;1163}1164SETTINGS_LOGGED = true;11651166String refType;1167switch (REF_TYPE) {1168default:1169case ReentrantContextProvider.REF_HARD:1170refType = "hard";1171break;1172case ReentrantContextProvider.REF_SOFT:1173refType = "soft";1174break;1175case ReentrantContextProvider.REF_WEAK:1176refType = "weak";1177break;1178}11791180logInfo("=========================================================="1181+ "=====================");11821183logInfo("Marlin software rasterizer = ENABLED");1184logInfo("Version = ["1185+ Version.getVersion() + "]");1186logInfo("sun.java2d.renderer = "1187+ reClass);1188logInfo("sun.java2d.renderer.useThreadLocal = "1189+ USE_THREAD_LOCAL);1190logInfo("sun.java2d.renderer.useRef = "1191+ refType);11921193logInfo("sun.java2d.renderer.edges = "1194+ MarlinConst.INITIAL_EDGES_COUNT);1195logInfo("sun.java2d.renderer.pixelWidth = "1196+ MarlinConst.INITIAL_PIXEL_WIDTH);1197logInfo("sun.java2d.renderer.pixelHeight = "1198+ MarlinConst.INITIAL_PIXEL_HEIGHT);11991200logInfo("sun.java2d.renderer.subPixel_log2_X = "1201+ MarlinConst.SUBPIXEL_LG_POSITIONS_X);1202logInfo("sun.java2d.renderer.subPixel_log2_Y = "1203+ MarlinConst.SUBPIXEL_LG_POSITIONS_Y);12041205logInfo("sun.java2d.renderer.tileSize_log2 = "1206+ MarlinConst.TILE_H_LG);1207logInfo("sun.java2d.renderer.tileWidth_log2 = "1208+ MarlinConst.TILE_W_LG);1209logInfo("sun.java2d.renderer.blockSize_log2 = "1210+ MarlinConst.BLOCK_SIZE_LG);12111212// RLE / blockFlags settings12131214logInfo("sun.java2d.renderer.forceRLE = "1215+ MarlinProperties.isForceRLE());1216logInfo("sun.java2d.renderer.forceNoRLE = "1217+ MarlinProperties.isForceNoRLE());1218logInfo("sun.java2d.renderer.useTileFlags = "1219+ MarlinProperties.isUseTileFlags());1220logInfo("sun.java2d.renderer.useTileFlags.useHeuristics = "1221+ MarlinProperties.isUseTileFlagsWithHeuristics());1222logInfo("sun.java2d.renderer.rleMinWidth = "1223+ MarlinCache.RLE_MIN_WIDTH);12241225// optimisation parameters1226logInfo("sun.java2d.renderer.useSimplifier = "1227+ MarlinConst.USE_SIMPLIFIER);1228logInfo("sun.java2d.renderer.usePathSimplifier= "1229+ MarlinConst.USE_PATH_SIMPLIFIER);1230logInfo("sun.java2d.renderer.pathSimplifier.pixTol = "1231+ MarlinProperties.getPathSimplifierPixelTolerance());12321233logInfo("sun.java2d.renderer.clip = "1234+ MarlinProperties.isDoClip());1235logInfo("sun.java2d.renderer.clip.runtime.enable = "1236+ MarlinProperties.isDoClipRuntimeFlag());12371238logInfo("sun.java2d.renderer.clip.subdivider = "1239+ MarlinProperties.isDoClipSubdivider());1240logInfo("sun.java2d.renderer.clip.subdivider.minLength = "1241+ MarlinProperties.getSubdividerMinLength());12421243// debugging parameters1244logInfo("sun.java2d.renderer.doStats = "1245+ MarlinConst.DO_STATS);1246logInfo("sun.java2d.renderer.doMonitors = "1247+ MarlinConst.DO_MONITORS);1248logInfo("sun.java2d.renderer.doChecks = "1249+ MarlinConst.DO_CHECKS);12501251// logging parameters1252logInfo("sun.java2d.renderer.useLogger = "1253+ MarlinConst.USE_LOGGER);1254logInfo("sun.java2d.renderer.logCreateContext = "1255+ MarlinConst.LOG_CREATE_CONTEXT);1256logInfo("sun.java2d.renderer.logUnsafeMalloc = "1257+ MarlinConst.LOG_UNSAFE_MALLOC);12581259// quality settings1260logInfo("sun.java2d.renderer.curve_len_err = "1261+ MarlinProperties.getCurveLengthError());1262logInfo("sun.java2d.renderer.cubic_dec_d2 = "1263+ MarlinProperties.getCubicDecD2());1264logInfo("sun.java2d.renderer.cubic_inc_d1 = "1265+ MarlinProperties.getCubicIncD1());1266logInfo("sun.java2d.renderer.quad_dec_d2 = "1267+ MarlinProperties.getQuadDecD2());12681269logInfo("Renderer settings:");1270logInfo("CUB_DEC_BND = " + Renderer.CUB_DEC_BND);1271logInfo("CUB_INC_BND = " + Renderer.CUB_INC_BND);1272logInfo("QUAD_DEC_BND = " + Renderer.QUAD_DEC_BND);12731274logInfo("INITIAL_EDGES_CAPACITY = "1275+ MarlinConst.INITIAL_EDGES_CAPACITY);1276logInfo("INITIAL_CROSSING_COUNT = "1277+ Renderer.INITIAL_CROSSING_COUNT);12781279logInfo("=========================================================="1280+ "=====================");1281}12821283/**1284* Get the RendererContext instance dedicated to the current thread1285* @return RendererContext instance1286*/1287@SuppressWarnings({"unchecked"})1288static RendererContext getRendererContext() {1289final RendererContext rdrCtx = RDR_CTX_PROVIDER.acquire();1290if (DO_MONITORS) {1291rdrCtx.stats.mon_pre_getAATileGenerator.start();1292}1293return rdrCtx;1294}12951296/**1297* Reset and return the given RendererContext instance for reuse1298* @param rdrCtx RendererContext instance1299*/1300static void returnRendererContext(final RendererContext rdrCtx) {1301rdrCtx.dispose();13021303if (DO_MONITORS) {1304rdrCtx.stats.mon_pre_getAATileGenerator.stop();1305}1306RDR_CTX_PROVIDER.release(rdrCtx);1307}1308}130913101311