/*
 * Copyright (c) 2007, 2013, 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.dc;

import java.awt.Shape;
import java.awt.BasicStroke;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.AffineTransform;

import sun.awt.geom.PathConsumer2D;
import sun.java2d.pipe.Region;
import sun.java2d.pipe.AATileGenerator;
import sun.java2d.pipe.RenderingEngine;

import sun.dc.pr.Rasterizer;
import sun.dc.pr.PathStroker;
import sun.dc.pr.PathDasher;
import sun.dc.pr.PRException;
import sun.dc.path.PathConsumer;
import sun.dc.path.PathException;
import sun.dc.path.FastPathProducer;

public class DuctusRenderingEngine extends RenderingEngine {
    static final float PenUnits = 0.01f;
    static final int MinPenUnits = 100;
    static final int MinPenUnitsAA = 20;
    static final float MinPenSizeAA = PenUnits * MinPenUnitsAA;

    static final float UPPER_BND = Float.MAX_VALUE / 2.0f;
    static final float LOWER_BND = -UPPER_BND;

    private static final int RasterizerCaps[] = {
        Rasterizer.BUTT, Rasterizer.ROUND, Rasterizer.SQUARE
    };

    private static final int RasterizerCorners[] = {
        Rasterizer.MITER, Rasterizer.ROUND, Rasterizer.BEVEL
    };

    static float[] getTransformMatrix(AffineTransform transform) {
        float matrix[] = new float[4];
        double dmatrix[] = new double[6];
        transform.getMatrix(dmatrix);
        for (int i = 0; i < 4; i++) {
            matrix[i] = (float) dmatrix[i];
        }
        return matrix;
    }

    
{@inheritDoc}
/** * {@inheritDoc} */
@Override public Shape createStrokedShape(Shape src, float width, int caps, int join, float miterlimit, float dashes[], float dashphase) { FillAdapter filler = new FillAdapter(); PathStroker stroker = new PathStroker(filler); PathDasher dasher = null; try { PathConsumer consumer; stroker.setPenDiameter(width); stroker.setPenT4(null); stroker.setCaps(RasterizerCaps[caps]); stroker.setCorners(RasterizerCorners[join], miterlimit); if (dashes != null) { dasher = new PathDasher(stroker); dasher.setDash(dashes, dashphase); dasher.setDashT4(null); consumer = dasher; } else { consumer = stroker; } feedConsumer(consumer, src.getPathIterator(null)); } finally { stroker.dispose(); if (dasher != null) { dasher.dispose(); } } return filler.getShape(); }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public void strokeTo(Shape src, AffineTransform transform, BasicStroke bs, boolean thin, boolean normalize, boolean antialias, PathConsumer2D sr) { PathStroker stroker = new PathStroker(sr); PathConsumer consumer = stroker; float matrix[] = null; if (!thin) { stroker.setPenDiameter(bs.getLineWidth()); if (transform != null) { matrix = getTransformMatrix(transform); } stroker.setPenT4(matrix); stroker.setPenFitting(PenUnits, MinPenUnits); } stroker.setCaps(RasterizerCaps[bs.getEndCap()]); stroker.setCorners(RasterizerCorners[bs.getLineJoin()], bs.getMiterLimit()); float[] dashes = bs.getDashArray(); if (dashes != null) { PathDasher dasher = new PathDasher(stroker); dasher.setDash(dashes, bs.getDashPhase()); if (transform != null && matrix == null) { matrix = getTransformMatrix(transform); } dasher.setDashT4(matrix); consumer = dasher; } try { PathIterator pi = src.getPathIterator(transform); feedConsumer(pi, consumer, normalize, 0.25f); } catch (PathException e) { throw new InternalError("Unable to Stroke shape ("+ e.getMessage()+")", e); } finally { while (consumer != null && consumer != sr) { PathConsumer next = consumer.getConsumer(); consumer.dispose(); consumer = next; } } } /* * Feed a path from a PathIterator to a Ductus PathConsumer. */ public static void feedConsumer(PathIterator pi, PathConsumer consumer, boolean normalize, float norm) throws PathException { consumer.beginPath(); boolean pathClosed = false; boolean skip = false; boolean subpathStarted = false; float mx = 0.0f; float my = 0.0f; float point[] = new float[6]; float rnd = (0.5f - norm); float ax = 0.0f; float ay = 0.0f; while (!pi.isDone()) { int type = pi.currentSegment(point); if (pathClosed == true) { pathClosed = false; if (type != PathIterator.SEG_MOVETO) { // Force current point back to last moveto point consumer.beginSubpath(mx, my); subpathStarted = true; } } if (normalize) { int index; switch (type) { case PathIterator.SEG_CUBICTO: index = 4; break; case PathIterator.SEG_QUADTO: index = 2; break; case PathIterator.SEG_MOVETO: case PathIterator.SEG_LINETO: index = 0; break; case PathIterator.SEG_CLOSE: default: index = -1; break; } if (index >= 0) { float ox = point[index]; float oy = point[index+1]; float newax = (float) Math.floor(ox + rnd) + norm; float neway = (float) Math.floor(oy + rnd) + norm; point[index] = newax; point[index+1] = neway; newax -= ox; neway -= oy; switch (type) { case PathIterator.SEG_CUBICTO: point[0] += ax; point[1] += ay; point[2] += newax; point[3] += neway; break; case PathIterator.SEG_QUADTO: point[0] += (newax + ax) / 2; point[1] += (neway + ay) / 2; break; case PathIterator.SEG_MOVETO: case PathIterator.SEG_LINETO: case PathIterator.SEG_CLOSE: break; } ax = newax; ay = neway; } } switch (type) { case PathIterator.SEG_MOVETO: /* Checking SEG_MOVETO coordinates if they are out of the * [LOWER_BND, UPPER_BND] range. This check also handles NaN * and Infinity values. Skipping next path segment in case of * invalid data. */ if (point[0] < UPPER_BND && point[0] > LOWER_BND && point[1] < UPPER_BND && point[1] > LOWER_BND) { mx = point[0]; my = point[1]; consumer.beginSubpath(mx, my); subpathStarted = true; skip = false; } else { skip = true; } break; case PathIterator.SEG_LINETO: /* Checking SEG_LINETO coordinates if they are out of the * [LOWER_BND, UPPER_BND] range. This check also handles NaN * and Infinity values. Ignoring current path segment in case * of invalid data. If segment is skipped its endpoint * (if valid) is used to begin new subpath. */ if (point[0] < UPPER_BND && point[0] > LOWER_BND && point[1] < UPPER_BND && point[1] > LOWER_BND) { if (skip) { consumer.beginSubpath(point[0], point[1]); subpathStarted = true; skip = false; } else { consumer.appendLine(point[0], point[1]); } } break; case PathIterator.SEG_QUADTO: // Quadratic curves take two points /* Checking SEG_QUADTO coordinates if they are out of the * [LOWER_BND, UPPER_BND] range. This check also handles NaN * and Infinity values. Ignoring current path segment in case * of invalid endpoints's data. Equivalent to the SEG_LINETO * if endpoint coordinates are valid but there are invalid data * among other coordinates */ if (point[2] < UPPER_BND && point[2] > LOWER_BND && point[3] < UPPER_BND && point[3] > LOWER_BND) { if (skip) { consumer.beginSubpath(point[2], point[3]); subpathStarted = true; skip = false; } else { if (point[0] < UPPER_BND && point[0] > LOWER_BND && point[1] < UPPER_BND && point[1] > LOWER_BND) { consumer.appendQuadratic(point[0], point[1], point[2], point[3]); } else { consumer.appendLine(point[2], point[3]); } } } break; case PathIterator.SEG_CUBICTO: // Cubic curves take three points /* Checking SEG_CUBICTO coordinates if they are out of the * [LOWER_BND, UPPER_BND] range. This check also handles NaN * and Infinity values. Ignoring current path segment in case * of invalid endpoints's data. Equivalent to the SEG_LINETO * if endpoint coordinates are valid but there are invalid data * among other coordinates */ if (point[4] < UPPER_BND && point[4] > LOWER_BND && point[5] < UPPER_BND && point[5] > LOWER_BND) { if (skip) { consumer.beginSubpath(point[4], point[5]); subpathStarted = true; skip = false; } else { if (point[0] < UPPER_BND && point[0] > LOWER_BND && point[1] < UPPER_BND && point[1] > LOWER_BND && point[2] < UPPER_BND && point[2] > LOWER_BND && point[3] < UPPER_BND && point[3] > LOWER_BND) { consumer.appendCubic(point[0], point[1], point[2], point[3], point[4], point[5]); } else { consumer.appendLine(point[4], point[5]); } } } break; case PathIterator.SEG_CLOSE: if (subpathStarted) { consumer.closedSubpath(); subpathStarted = false; pathClosed = true; } break; } pi.next(); } consumer.endPath(); } private static Rasterizer theRasterizer; public synchronized static Rasterizer getRasterizer() { Rasterizer r = theRasterizer; if (r == null) { r = new Rasterizer(); } else { theRasterizer = null; } return r; } public synchronized static void dropRasterizer(Rasterizer r) { r.reset(); theRasterizer = r; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public float getMinimumAAPenSize() { return MinPenSizeAA; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public AATileGenerator getAATileGenerator(Shape s, AffineTransform at, Region clip, BasicStroke bs, boolean thin, boolean normalize, int bbox[]) { Rasterizer r = getRasterizer(); PathIterator pi = s.getPathIterator(at); if (bs != null) { float matrix[] = null; r.setUsage(Rasterizer.STROKE); if (thin) { r.setPenDiameter(MinPenSizeAA); } else { r.setPenDiameter(bs.getLineWidth()); if (at != null) { matrix = getTransformMatrix(at); r.setPenT4(matrix); } r.setPenFitting(PenUnits, MinPenUnitsAA); } r.setCaps(RasterizerCaps[bs.getEndCap()]); r.setCorners(RasterizerCorners[bs.getLineJoin()], bs.getMiterLimit()); float[] dashes = bs.getDashArray(); if (dashes != null) { r.setDash(dashes, bs.getDashPhase()); if (at != null && matrix == null) { matrix = getTransformMatrix(at); } r.setDashT4(matrix); } } else { r.setUsage(pi.getWindingRule() == PathIterator.WIND_EVEN_ODD ? Rasterizer.EOFILL : Rasterizer.NZFILL); } r.beginPath(); { boolean pathClosed = false; boolean skip = false; boolean subpathStarted = false; float mx = 0.0f; float my = 0.0f; float point[] = new float[6]; float ax = 0.0f; float ay = 0.0f; while (!pi.isDone()) { int type = pi.currentSegment(point); if (pathClosed == true) { pathClosed = false; if (type != PathIterator.SEG_MOVETO) { // Force current point back to last moveto point r.beginSubpath(mx, my); subpathStarted = true; } } if (normalize) { int index; switch (type) { case PathIterator.SEG_CUBICTO: index = 4; break; case PathIterator.SEG_QUADTO: index = 2; break; case PathIterator.SEG_MOVETO: case PathIterator.SEG_LINETO: index = 0; break; case PathIterator.SEG_CLOSE: default: index = -1; break; } if (index >= 0) { float ox = point[index]; float oy = point[index+1]; float newax = (float) Math.floor(ox) + 0.5f; float neway = (float) Math.floor(oy) + 0.5f; point[index] = newax; point[index+1] = neway; newax -= ox; neway -= oy; switch (type) { case PathIterator.SEG_CUBICTO: point[0] += ax; point[1] += ay; point[2] += newax; point[3] += neway; break; case PathIterator.SEG_QUADTO: point[0] += (newax + ax) / 2; point[1] += (neway + ay) / 2; break; case PathIterator.SEG_MOVETO: case PathIterator.SEG_LINETO: case PathIterator.SEG_CLOSE: break; } ax = newax; ay = neway; } } switch (type) { case PathIterator.SEG_MOVETO: /* Checking SEG_MOVETO coordinates if they are out of the * [LOWER_BND, UPPER_BND] range. This check also handles NaN * and Infinity values. Skipping next path segment in case * of invalid data. */ if (point[0] < UPPER_BND && point[0] > LOWER_BND && point[1] < UPPER_BND && point[1] > LOWER_BND) { mx = point[0]; my = point[1]; r.beginSubpath(mx, my); subpathStarted = true; skip = false; } else { skip = true; } break; case PathIterator.SEG_LINETO: /* Checking SEG_LINETO coordinates if they are out of the * [LOWER_BND, UPPER_BND] range. This check also handles * NaN and Infinity values. Ignoring current path segment * in case of invalid data. If segment is skipped its * endpoint (if valid) is used to begin new subpath. */ if (point[0] < UPPER_BND && point[0] > LOWER_BND && point[1] < UPPER_BND && point[1] > LOWER_BND) { if (skip) { r.beginSubpath(point[0], point[1]); subpathStarted = true; skip = false; } else { r.appendLine(point[0], point[1]); } } break; case PathIterator.SEG_QUADTO: // Quadratic curves take two points /* Checking SEG_QUADTO coordinates if they are out of the * [LOWER_BND, UPPER_BND] range. This check also handles * NaN and Infinity values. Ignoring current path segment * in case of invalid endpoints's data. Equivalent to the * SEG_LINETO if endpoint coordinates are valid but there * are invalid data among other coordinates */ if (point[2] < UPPER_BND && point[2] > LOWER_BND && point[3] < UPPER_BND && point[3] > LOWER_BND) { if (skip) { r.beginSubpath(point[2], point[3]); subpathStarted = true; skip = false; } else { if (point[0] < UPPER_BND && point[0] > LOWER_BND && point[1] < UPPER_BND && point[1] > LOWER_BND) { r.appendQuadratic(point[0], point[1], point[2], point[3]); } else { r.appendLine(point[2], point[3]); } } } break; case PathIterator.SEG_CUBICTO: // Cubic curves take three points /* Checking SEG_CUBICTO coordinates if they are out of the * [LOWER_BND, UPPER_BND] range. This check also handles * NaN and Infinity values. Ignoring current path segment * in case of invalid endpoints's data. Equivalent to the * SEG_LINETO if endpoint coordinates are valid but there * are invalid data among other coordinates */ if (point[4] < UPPER_BND && point[4] > LOWER_BND && point[5] < UPPER_BND && point[5] > LOWER_BND) { if (skip) { r.beginSubpath(point[4], point[5]); subpathStarted = true; skip = false; } else { if (point[0] < UPPER_BND && point[0] > LOWER_BND && point[1] < UPPER_BND && point[1] > LOWER_BND && point[2] < UPPER_BND && point[2] > LOWER_BND && point[3] < UPPER_BND && point[3] > LOWER_BND) { r.appendCubic(point[0], point[1], point[2], point[3], point[4], point[5]); } else { r.appendLine(point[4], point[5]); } } } break; case PathIterator.SEG_CLOSE: if (subpathStarted) { r.closedSubpath(); subpathStarted = false; pathClosed = true; } break; } pi.next(); } } try { r.endPath(); r.getAlphaBox(bbox); clip.clipBoxToBounds(bbox); if (bbox[0] >= bbox[2] || bbox[1] >= bbox[3]) { dropRasterizer(r); return null; } r.setOutputArea(bbox[0], bbox[1], bbox[2] - bbox[0], bbox[3] - bbox[1]); } catch (PRException e) { /* * This exeption is thrown from the native part of the Ductus * (only in case of a debug build) to indicate that some * segments of the path have very large coordinates. * See 4485298 for more info. */ System.err.println("DuctusRenderingEngine.getAATileGenerator: "+e); } return r; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public AATileGenerator getAATileGenerator(double x, double y, double dx1, double dy1, double dx2, double dy2, double lw1, double lw2, Region clip, int bbox[]) { // REMIND: Deal with large coordinates! double ldx1, ldy1, ldx2, ldy2; boolean innerpgram = (lw1 > 0 && lw2 > 0); if (innerpgram) { ldx1 = dx1 * lw1; ldy1 = dy1 * lw1; ldx2 = dx2 * lw2; ldy2 = dy2 * lw2; x -= (ldx1 + ldx2) / 2.0; y -= (ldy1 + ldy2) / 2.0; dx1 += ldx1; dy1 += ldy1; dx2 += ldx2; dy2 += ldy2; if (lw1 > 1 && lw2 > 1) { // Inner parallelogram was entirely consumed by stroke... innerpgram = false; } } else { ldx1 = ldy1 = ldx2 = ldy2 = 0; } Rasterizer r = getRasterizer(); r.setUsage(Rasterizer.EOFILL); r.beginPath(); r.beginSubpath((float) x, (float) y); r.appendLine((float) (x+dx1), (float) (y+dy1)); r.appendLine((float) (x+dx1+dx2), (float) (y+dy1+dy2)); r.appendLine((float) (x+dx2), (float) (y+dy2)); r.closedSubpath(); if (innerpgram) { x += ldx1 + ldx2; y += ldy1 + ldy2; dx1 -= 2.0 * ldx1; dy1 -= 2.0 * ldy1; dx2 -= 2.0 * ldx2; dy2 -= 2.0 * ldy2; r.beginSubpath((float) x, (float) y); r.appendLine((float) (x+dx1), (float) (y+dy1)); r.appendLine((float) (x+dx1+dx2), (float) (y+dy1+dy2)); r.appendLine((float) (x+dx2), (float) (y+dy2)); r.closedSubpath(); } try { r.endPath(); r.getAlphaBox(bbox); clip.clipBoxToBounds(bbox); if (bbox[0] >= bbox[2] || bbox[1] >= bbox[3]) { dropRasterizer(r); return null; } r.setOutputArea(bbox[0], bbox[1], bbox[2] - bbox[0], bbox[3] - bbox[1]); } catch (PRException e) { /* * This exeption is thrown from the native part of the Ductus * (only in case of a debug build) to indicate that some * segments of the path have very large coordinates. * See 4485298 for more info. */ System.err.println("DuctusRenderingEngine.getAATileGenerator: "+e); } return r; } private void feedConsumer(PathConsumer consumer, PathIterator pi) { try { consumer.beginPath(); boolean pathClosed = false; float mx = 0.0f; float my = 0.0f; float point[] = new float[6]; while (!pi.isDone()) { int type = pi.currentSegment(point); if (pathClosed == true) { pathClosed = false; if (type != PathIterator.SEG_MOVETO) { // Force current point back to last moveto point consumer.beginSubpath(mx, my); } } switch (type) { case PathIterator.SEG_MOVETO: mx = point[0]; my = point[1]; consumer.beginSubpath(point[0], point[1]); break; case PathIterator.SEG_LINETO: consumer.appendLine(point[0], point[1]); break; case PathIterator.SEG_QUADTO: consumer.appendQuadratic(point[0], point[1], point[2], point[3]); break; case PathIterator.SEG_CUBICTO: consumer.appendCubic(point[0], point[1], point[2], point[3], point[4], point[5]); break; case PathIterator.SEG_CLOSE: consumer.closedSubpath(); pathClosed = true; break; } pi.next(); } consumer.endPath(); } catch (PathException e) { throw new InternalError("Unable to Stroke shape ("+ e.getMessage()+")", e); } } private class FillAdapter implements PathConsumer { boolean closed; Path2D.Float path; public FillAdapter() { // Ductus only supplies float coordinates so // Path2D.Double is not necessary here. path = new Path2D.Float(Path2D.WIND_NON_ZERO); } public Shape getShape() { return path; } public void dispose() { } public PathConsumer getConsumer() { return null; } public void beginPath() {} public void beginSubpath(float x0, float y0) { if (closed) { path.closePath(); closed = false; } path.moveTo(x0, y0); } public void appendLine(float x1, float y1) { path.lineTo(x1, y1); } public void appendQuadratic(float xm, float ym, float x1, float y1) { path.quadTo(xm, ym, x1, y1); } public void appendCubic(float xm, float ym, float xn, float yn, float x1, float y1) { path.curveTo(xm, ym, xn, yn, x1, y1); } public void closedSubpath() { closed = true; } public void endPath() { if (closed) { path.closePath(); closed = false; } } public void useProxy(FastPathProducer proxy) throws PathException { proxy.sendTo(this); } public long getCPathConsumer() { return 0; } } }