/* * Copyright 2008 Sun Microsystems, Inc. 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. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package ae.sun.java2d.pipe; import ae.java.awt.Shape; import ae.java.awt.BasicStroke; import ae.java.awt.geom.Line2D; import ae.java.awt.geom.Rectangle2D; import ae.java.awt.geom.AffineTransform; import ae.sun.awt.SunHints; import ae.sun.java2d.SunGraphics2D; /** * This class converts calls to the basic pixel rendering methods * into calls to the methods on a ParallelogramPipe. * Most calls are transformed into calls to the fill(Shape) method * by the parent PixelToShapeConverter class, but some calls are * transformed into calls to fill/drawParallelogram(). */ public class PixelToParallelogramConverter extends PixelToShapeConverter implements ShapeDrawPipe { ParallelogramPipe outrenderer; double minPenSize; double normPosition; double normRoundingBias; boolean adjustfill; /** * @param shapepipe pipeline to forward shape calls to * @param pgrampipe pipeline to forward parallelogram calls to * (and drawLine calls if possible) * @param minPenSize minimum pen size for dropout control * @param normPosition sub-pixel location to normalize endpoints * for STROKE_NORMALIZE cases * @param adjustFill boolean to control whethere normalization * constants are also applied to fill operations * (normally true for non-AA, false for AA) */ public PixelToParallelogramConverter(ShapeDrawPipe shapepipe, ParallelogramPipe pgrampipe, double minPenSize, double normPosition, boolean adjustfill) { super(shapepipe); outrenderer = pgrampipe; this.minPenSize = minPenSize; this.normPosition = normPosition; this.normRoundingBias = 0.5 - normPosition; this.adjustfill = adjustfill; } public void drawLine(SunGraphics2D sg2d, int x1, int y1, int x2, int y2) { if (!drawGeneralLine(sg2d, x1, y1, x2, y2)) { super.drawLine(sg2d, x1, y1, x2, y2); } } public void drawRect(SunGraphics2D sg2d, int x, int y, int w, int h) { if (w >= 0 && h >= 0) { if (sg2d.strokeState < SunGraphics2D.STROKE_CUSTOM) { BasicStroke bs = ((BasicStroke) sg2d.stroke); if (w > 0 && h > 0) { if (bs.getLineJoin() == BasicStroke.JOIN_MITER && bs.getDashArray() == null) { double lw = bs.getLineWidth(); drawRectangle(sg2d, x, y, w, h, lw); return; } } else { // Note: This calls the integer version which // will verify that the local drawLine optimizations // work and call super.drawLine(), if not. drawLine(sg2d, x, y, x+w, y+h); return; } } super.drawRect(sg2d, x, y, w, h); } } public void fillRect(SunGraphics2D sg2d, int x, int y, int w, int h) { if (w > 0 && h > 0) { fillRectangle(sg2d, x, y, w, h); } } public void draw(SunGraphics2D sg2d, Shape s) { if (sg2d.strokeState < SunGraphics2D.STROKE_CUSTOM) { BasicStroke bs = ((BasicStroke) sg2d.stroke); if (s instanceof Rectangle2D) { if (bs.getLineJoin() == BasicStroke.JOIN_MITER && bs.getDashArray() == null) { Rectangle2D r2d = (Rectangle2D) s; double w = r2d.getWidth(); double h = r2d.getHeight(); double x = r2d.getX(); double y = r2d.getY(); if (w >= 0 && h >= 0) { double lw = bs.getLineWidth(); drawRectangle(sg2d, x, y, w, h, lw); } return; } } else if (s instanceof Line2D) { Line2D l2d = (Line2D) s; if (drawGeneralLine(sg2d, l2d.getX1(), l2d.getY1(), l2d.getX2(), l2d.getY2())) { return; } } } outpipe.draw(sg2d, s); } public void fill(SunGraphics2D sg2d, Shape s) { if (s instanceof Rectangle2D) { Rectangle2D r2d = (Rectangle2D) s; double w = r2d.getWidth(); double h = r2d.getHeight(); if (w > 0 && h > 0) { double x = r2d.getX(); double y = r2d.getY(); fillRectangle(sg2d, x, y, w, h); } return; } outpipe.fill(sg2d, s); } static double len(double x, double y) { return ((x == 0) ? Math.abs(y) : ((y == 0) ? Math.abs(x) : Math.sqrt(x * x + y * y))); } double normalize(double v) { return Math.floor(v + normRoundingBias) + normPosition; } public boolean drawGeneralLine(SunGraphics2D sg2d, double x1, double y1, double x2, double y2) { if (sg2d.strokeState == SunGraphics2D.STROKE_CUSTOM || sg2d.strokeState == SunGraphics2D.STROKE_THINDASHED) { return false; } BasicStroke bs = (BasicStroke) sg2d.stroke; int cap = bs.getEndCap(); if (cap == BasicStroke.CAP_ROUND || bs.getDashArray() != null) { // TODO: we could construct the GeneralPath directly // for CAP_ROUND and save a lot of processing in that case... // And again, we would need to deal with dropout control... return false; } double lw = bs.getLineWidth(); // Save the original dx, dy in case we need it to transform // the linewidth as a perpendicular vector below double dx = x2 - x1; double dy = y2 - y1; switch (sg2d.transformState) { case SunGraphics2D.TRANSFORM_GENERIC: case SunGraphics2D.TRANSFORM_TRANSLATESCALE: { double coords[] = {x1, y1, x2, y2}; sg2d.transform.transform(coords, 0, coords, 0, 2); x1 = coords[0]; y1 = coords[1]; x2 = coords[2]; y2 = coords[3]; } break; case SunGraphics2D.TRANSFORM_ANY_TRANSLATE: case SunGraphics2D.TRANSFORM_INT_TRANSLATE: { double tx = sg2d.transform.getTranslateX(); double ty = sg2d.transform.getTranslateY(); x1 += tx; y1 += ty; x2 += tx; y2 += ty; } break; case SunGraphics2D.TRANSFORM_ISIDENT: break; default: throw new InternalError("unknown TRANSFORM state..."); } if (sg2d.strokeHint != SunHints.INTVAL_STROKE_PURE) { if (sg2d.strokeState == SunGraphics2D.STROKE_THIN && outrenderer instanceof PixelDrawPipe) { // PixelDrawPipes will add sg2d.transXY so we need to factor // that out... int ix1 = (int) Math.floor(x1 - sg2d.transX); int iy1 = (int) Math.floor(y1 - sg2d.transY); int ix2 = (int) Math.floor(x2 - sg2d.transX); int iy2 = (int) Math.floor(y2 - sg2d.transY); ((PixelDrawPipe)outrenderer).drawLine(sg2d, ix1, iy1, ix2, iy2); return true; } x1 = normalize(x1); y1 = normalize(y1); x2 = normalize(x2); y2 = normalize(y2); } if (sg2d.transformState >= SunGraphics2D.TRANSFORM_TRANSLATESCALE) { // Transform the linewidth... // calculate the scaling factor for a unit vector // perpendicular to the original user space line. double len = len(dx, dy); if (len == 0) { dx = len = 1; // dy = 0; already } // delta transform the transposed (90 degree rotated) unit vector double unitvector[] = {dy/len, -dx/len}; sg2d.transform.deltaTransform(unitvector, 0, unitvector, 0, 1); lw *= len(unitvector[0], unitvector[1]); } lw = Math.max(lw, minPenSize); dx = x2 - x1; dy = y2 - y1; double len = len(dx, dy); double udx, udy; if (len == 0) { if (cap == BasicStroke.CAP_BUTT) { return true; } udx = lw; udy = 0; } else { udx = lw * dx / len; udy = lw * dy / len; } double px = x1 + udy / 2.0; double py = y1 - udx / 2.0; if (cap == BasicStroke.CAP_SQUARE) { px -= udx / 2.0; py -= udy / 2.0; dx += udx; dy += udy; } outrenderer.fillParallelogram(sg2d, px, py, -udy, udx, dx, dy); return true; } public void fillRectangle(SunGraphics2D sg2d, double rx, double ry, double rw, double rh) { double px, py; double dx1, dy1, dx2, dy2; AffineTransform txform = sg2d.transform; dx1 = txform.getScaleX(); dy1 = txform.getShearY(); dx2 = txform.getShearX(); dy2 = txform.getScaleY(); px = rx * dx1 + ry * dx2 + txform.getTranslateX(); py = rx * dy1 + ry * dy2 + txform.getTranslateY(); dx1 *= rw; dy1 *= rw; dx2 *= rh; dy2 *= rh; if (adjustfill && sg2d.strokeState < SunGraphics2D.STROKE_CUSTOM && sg2d.strokeHint != SunHints.INTVAL_STROKE_PURE) { double newx = normalize(px); double newy = normalize(py); dx1 = normalize(px + dx1) - newx; dy1 = normalize(py + dy1) - newy; dx2 = normalize(px + dx2) - newx; dy2 = normalize(py + dy2) - newy; px = newx; py = newy; } outrenderer.fillParallelogram(sg2d, px, py, dx1, dy1, dx2, dy2); } public void drawRectangle(SunGraphics2D sg2d, double rx, double ry, double rw, double rh, double lw) { double px, py; double dx1, dy1, dx2, dy2; double lw1, lw2; AffineTransform txform = sg2d.transform; dx1 = txform.getScaleX(); dy1 = txform.getShearY(); dx2 = txform.getShearX(); dy2 = txform.getScaleY(); px = rx * dx1 + ry * dx2 + txform.getTranslateX(); py = rx * dy1 + ry * dy2 + txform.getTranslateY(); // lw along dx1,dy1 scale by transformed length of dx2,dy2 vectors // and vice versa lw1 = len(dx1, dy1) * lw; lw2 = len(dx2, dy2) * lw; dx1 *= rw; dy1 *= rw; dx2 *= rh; dy2 *= rh; if (sg2d.strokeState < SunGraphics2D.STROKE_CUSTOM && sg2d.strokeHint != SunHints.INTVAL_STROKE_PURE) { double newx = normalize(px); double newy = normalize(py); dx1 = normalize(px + dx1) - newx; dy1 = normalize(py + dy1) - newy; dx2 = normalize(px + dx2) - newx; dy2 = normalize(py + dy2) - newy; px = newx; py = newy; } lw1 = Math.max(lw1, minPenSize); lw2 = Math.max(lw2, minPenSize); double len1 = len(dx1, dy1); double len2 = len(dx2, dy2); if (lw1 >= len1 || lw2 >= len2) { // The line widths are large enough to consume the // entire hole in the middle of the parallelogram // so we can just fill the outer parallelogram. fillOuterParallelogram(sg2d, px, py, dx1, dy1, dx2, dy2, len1, len2, lw1, lw2); } else { outrenderer.drawParallelogram(sg2d, px, py, dx1, dy1, dx2, dy2, lw1 / len1, lw2 / len2); } } /** * This utility function handles the case where a drawRectangle * operation discovered that the interior hole in the rectangle * or parallelogram has been completely filled in by the stroke * width. It calculates the outer parallelogram of the stroke * and issues a single fillParallelogram request to fill it. */ public void fillOuterParallelogram(SunGraphics2D sg2d, double px, double py, double dx1, double dy1, double dx2, double dy2, double len1, double len2, double lw1, double lw2) { double udx1 = dx1 / len1; double udy1 = dy1 / len1; double udx2 = dx2 / len2; double udy2 = dy2 / len2; if (len1 == 0) { // len1 is 0, replace udxy1 with perpendicular of udxy2 if (len2 == 0) { // both are 0, use a unit Y vector for udxy2 udx2 = 0; udy2 = 1; } udx1 = udy2; udy1 = -udx2; } else if (len2 == 0) { // len2 is 0, replace udxy2 with perpendicular of udxy1 udx2 = udy1; udy2 = -udx1; } udx1 *= lw1; udy1 *= lw1; udx2 *= lw2; udy2 *= lw2; px -= (udx1 + udx2) / 2; py -= (udy1 + udy2) / 2; dx1 += udx1; dy1 += udy1; dx2 += udx2; dy2 += udy2; outrenderer.fillParallelogram(sg2d, px, py, dx1, dy1, dx2, dy2); } }