/* * Copyright (c) 2005, 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.java2d.pipe; import java.awt.BasicStroke; import java.awt.Polygon; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Arc2D; import java.awt.geom.Ellipse2D; import java.awt.geom.Path2D; import java.awt.geom.IllegalPathStateException; import java.awt.geom.PathIterator; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; import sun.java2d.SunGraphics2D; import sun.java2d.loops.ProcessPath; import static sun.java2d.pipe.BufferedOpCodes.*; /** * Base class for enqueuing rendering operations in a single-threaded * rendering environment. Instead of each operation being rendered * immediately by the underlying graphics library, the operation will be * added to the provided RenderQueue, which will be processed at a later * time by a single thread. * * This class provides implementations of drawLine(), drawRect(), drawPoly(), * fillRect(), draw(Shape), and fill(Shape), which are useful for a * hardware-accelerated renderer. The other draw*() and fill*() methods * simply delegate to draw(Shape) and fill(Shape), respectively. */ public abstract class BufferedRenderPipe implements PixelDrawPipe, PixelFillPipe, ShapeDrawPipe, ParallelogramPipe { ParallelogramPipe aapgrampipe = new AAParallelogramPipe(); static final int BYTES_PER_POLY_POINT = 8; static final int BYTES_PER_SCANLINE = 12; static final int BYTES_PER_SPAN = 16; protected RenderQueue rq; protected RenderBuffer buf; private BufferedDrawHandler drawHandler; public BufferedRenderPipe(RenderQueue rq) { this.rq = rq; this.buf = rq.getBuffer(); this.drawHandler = new BufferedDrawHandler(); } public ParallelogramPipe getAAParallelogramPipe() { return aapgrampipe; } /** * Validates the state in the provided SunGraphics2D object and sets up * any special resources for this operation (e.g. enabling gradient * shading). */ protected abstract void validateContext(SunGraphics2D sg2d); protected abstract void validateContextAA(SunGraphics2D sg2d); public void drawLine(SunGraphics2D sg2d, int x1, int y1, int x2, int y2) { int transx = sg2d.transX; int transy = sg2d.transY; rq.lock(); try { validateContext(sg2d); rq.ensureCapacity(20); buf.putInt(DRAW_LINE); buf.putInt(x1 + transx); buf.putInt(y1 + transy); buf.putInt(x2 + transx); buf.putInt(y2 + transy); } finally { rq.unlock(); } } public void drawRect(SunGraphics2D sg2d, int x, int y, int width, int height) { rq.lock(); try { validateContext(sg2d); rq.ensureCapacity(20); buf.putInt(DRAW_RECT); buf.putInt(x + sg2d.transX); buf.putInt(y + sg2d.transY); buf.putInt(width); buf.putInt(height); } finally { rq.unlock(); } } public void fillRect(SunGraphics2D sg2d, int x, int y, int width, int height) { rq.lock(); try { validateContext(sg2d); rq.ensureCapacity(20); buf.putInt(FILL_RECT); buf.putInt(x + sg2d.transX); buf.putInt(y + sg2d.transY); buf.putInt(width); buf.putInt(height); } finally { rq.unlock(); } } public void drawRoundRect(SunGraphics2D sg2d, int x, int y, int width, int height, int arcWidth, int arcHeight) { draw(sg2d, new RoundRectangle2D.Float(x, y, width, height, arcWidth, arcHeight)); } public void fillRoundRect(SunGraphics2D sg2d, int x, int y, int width, int height, int arcWidth, int arcHeight) { fill(sg2d, new RoundRectangle2D.Float(x, y, width, height, arcWidth, arcHeight)); } public void drawOval(SunGraphics2D sg2d, int x, int y, int width, int height) { draw(sg2d, new Ellipse2D.Float(x, y, width, height)); } public void fillOval(SunGraphics2D sg2d, int x, int y, int width, int height) { fill(sg2d, new Ellipse2D.Float(x, y, width, height)); } public void drawArc(SunGraphics2D sg2d, int x, int y, int width, int height, int startAngle, int arcAngle) { draw(sg2d, new Arc2D.Float(x, y, width, height, startAngle, arcAngle, Arc2D.OPEN)); } public void fillArc(SunGraphics2D sg2d, int x, int y, int width, int height, int startAngle, int arcAngle) { fill(sg2d, new Arc2D.Float(x, y, width, height, startAngle, arcAngle, Arc2D.PIE)); } protected void drawPoly(final SunGraphics2D sg2d, final int[] xPoints, final int[] yPoints, final int nPoints, final boolean isClosed) { if (xPoints == null || yPoints == null) { throw new NullPointerException("coordinate array"); } if (xPoints.length < nPoints || yPoints.length < nPoints) { throw new ArrayIndexOutOfBoundsException("coordinate array"); } if (nPoints < 2) { // render nothing return; } else if (nPoints == 2 && !isClosed) { // render a simple line drawLine(sg2d, xPoints[0], yPoints[0], xPoints[1], yPoints[1]); return; } rq.lock(); try { validateContext(sg2d); int pointBytesRequired = nPoints * BYTES_PER_POLY_POINT; int totalBytesRequired = 20 + pointBytesRequired; if (totalBytesRequired <= buf.capacity()) { if (totalBytesRequired > buf.remaining()) { // process the queue first and then enqueue the points rq.flushNow(); } buf.putInt(DRAW_POLY); // enqueue parameters buf.putInt(nPoints); buf.putInt(isClosed ? 1 : 0); buf.putInt(sg2d.transX); buf.putInt(sg2d.transY); // enqueue the points buf.put(xPoints, 0, nPoints); buf.put(yPoints, 0, nPoints); } else { // queue is too small to accommodate all points; perform the // operation directly on the queue flushing thread rq.flushAndInvokeNow(new Runnable() { public void run() { drawPoly(xPoints, yPoints, nPoints, isClosed, sg2d.transX, sg2d.transY); } }); } } finally { rq.unlock(); } } protected abstract void drawPoly(int[] xPoints, int[] yPoints, int nPoints, boolean isClosed, int transX, int transY); public void drawPolyline(SunGraphics2D sg2d, int[] xPoints, int[] yPoints, int nPoints) { drawPoly(sg2d, xPoints, yPoints, nPoints, false); } public void drawPolygon(SunGraphics2D sg2d, int[] xPoints, int[] yPoints, int nPoints) { drawPoly(sg2d, xPoints, yPoints, nPoints, true); } public void fillPolygon(SunGraphics2D sg2d, int[] xPoints, int[] yPoints, int nPoints) { fill(sg2d, new Polygon(xPoints, yPoints, nPoints)); } private class BufferedDrawHandler extends ProcessPath.DrawHandler { BufferedDrawHandler() { // these are bogus values; the caller will use validate() // to ensure that they are set properly prior to each usage super(0, 0, 0, 0); } /** * This method needs to be called prior to each draw/fillPath() * operation to ensure the clip bounds are up to date. */ void validate(SunGraphics2D sg2d) { Region clip = sg2d.getCompClip(); setBounds(clip.getLoX(), clip.getLoY(), clip.getHiX(), clip.getHiY(), sg2d.strokeHint); } /** * drawPath() support... */ public void drawLine(int x1, int y1, int x2, int y2) { // assert rq.lock.isHeldByCurrentThread(); rq.ensureCapacity(20); buf.putInt(DRAW_LINE); buf.putInt(x1); buf.putInt(y1); buf.putInt(x2); buf.putInt(y2); } public void drawPixel(int x, int y) { // assert rq.lock.isHeldByCurrentThread(); rq.ensureCapacity(12); buf.putInt(DRAW_PIXEL); buf.putInt(x); buf.putInt(y); } /** * fillPath() support... */ private int scanlineCount; private int scanlineCountIndex; private int remainingScanlines; private void resetFillPath() { buf.putInt(DRAW_SCANLINES); scanlineCountIndex = buf.position(); buf.putInt(0); scanlineCount = 0; remainingScanlines = buf.remaining() / BYTES_PER_SCANLINE; } private void updateScanlineCount() { buf.putInt(scanlineCountIndex, scanlineCount); } /** * Called from fillPath() to indicate that we are about to * start issuing drawScanline() calls. */ public void startFillPath() { rq.ensureCapacity(20); // to ensure room for at least a scanline resetFillPath(); } public void drawScanline(int x1, int x2, int y) { if (remainingScanlines == 0) { updateScanlineCount(); rq.flushNow(); resetFillPath(); } buf.putInt(x1); buf.putInt(x2); buf.putInt(y); scanlineCount++; remainingScanlines--; } /** * Called from fillPath() to indicate that we are done * issuing drawScanline() calls. */ public void endFillPath() { updateScanlineCount(); } } protected void drawPath(SunGraphics2D sg2d, Path2D.Float p2df, int transx, int transy) { rq.lock(); try { validateContext(sg2d); drawHandler.validate(sg2d); ProcessPath.drawPath(drawHandler, p2df, transx, transy); } finally { rq.unlock(); } } protected void fillPath(SunGraphics2D sg2d, Path2D.Float p2df, int transx, int transy) { rq.lock(); try { validateContext(sg2d); drawHandler.validate(sg2d); drawHandler.startFillPath(); ProcessPath.fillPath(drawHandler, p2df, transx, transy); drawHandler.endFillPath(); } finally { rq.unlock(); } } private native int fillSpans(RenderQueue rq, long buf, int pos, int limit, SpanIterator si, long iterator, int transx, int transy); protected void fillSpans(SunGraphics2D sg2d, SpanIterator si, int transx, int transy) { rq.lock(); try { validateContext(sg2d); rq.ensureCapacity(24); // so that we have room for at least a span int newpos = fillSpans(rq, buf.getAddress(), buf.position(), buf.capacity(), si, si.getNativeIterator(), transx, transy); buf.position(newpos); } finally { rq.unlock(); } } public void fillParallelogram(SunGraphics2D sg2d, double ux1, double uy1, double ux2, double uy2, double x, double y, double dx1, double dy1, double dx2, double dy2) { rq.lock(); try { validateContext(sg2d); rq.ensureCapacity(28); buf.putInt(FILL_PARALLELOGRAM); buf.putFloat((float) x); buf.putFloat((float) y); buf.putFloat((float) dx1); buf.putFloat((float) dy1); buf.putFloat((float) dx2); buf.putFloat((float) dy2); } finally { rq.unlock(); } } public void drawParallelogram(SunGraphics2D sg2d, double ux1, double uy1, double ux2, double uy2, double x, double y, double dx1, double dy1, double dx2, double dy2, double lw1, double lw2) { rq.lock(); try { validateContext(sg2d); rq.ensureCapacity(36); buf.putInt(DRAW_PARALLELOGRAM); buf.putFloat((float) x); buf.putFloat((float) y); buf.putFloat((float) dx1); buf.putFloat((float) dy1); buf.putFloat((float) dx2); buf.putFloat((float) dy2); buf.putFloat((float) lw1); buf.putFloat((float) lw2); } finally { rq.unlock(); } } private class AAParallelogramPipe implements ParallelogramPipe { public void fillParallelogram(SunGraphics2D sg2d, double ux1, double uy1, double ux2, double uy2, double x, double y, double dx1, double dy1, double dx2, double dy2) { rq.lock(); try { validateContextAA(sg2d); rq.ensureCapacity(28); buf.putInt(FILL_AAPARALLELOGRAM); buf.putFloat((float) x); buf.putFloat((float) y); buf.putFloat((float) dx1); buf.putFloat((float) dy1); buf.putFloat((float) dx2); buf.putFloat((float) dy2); } finally { rq.unlock(); } } public void drawParallelogram(SunGraphics2D sg2d, double ux1, double uy1, double ux2, double uy2, double x, double y, double dx1, double dy1, double dx2, double dy2, double lw1, double lw2) { rq.lock(); try { validateContextAA(sg2d); rq.ensureCapacity(36); buf.putInt(DRAW_AAPARALLELOGRAM); buf.putFloat((float) x); buf.putFloat((float) y); buf.putFloat((float) dx1); buf.putFloat((float) dy1); buf.putFloat((float) dx2); buf.putFloat((float) dy2); buf.putFloat((float) lw1); buf.putFloat((float) lw2); } finally { rq.unlock(); } } } public void draw(SunGraphics2D sg2d, Shape s) { if (sg2d.strokeState == SunGraphics2D.STROKE_THIN) { if (s instanceof Polygon) { if (sg2d.transformState < SunGraphics2D.TRANSFORM_TRANSLATESCALE) { Polygon p = (Polygon)s; drawPolygon(sg2d, p.xpoints, p.ypoints, p.npoints); return; } } Path2D.Float p2df; int transx, transy; if (sg2d.transformState <= SunGraphics2D.TRANSFORM_INT_TRANSLATE) { if (s instanceof Path2D.Float) { p2df = (Path2D.Float)s; } else { p2df = new Path2D.Float(s); } transx = sg2d.transX; transy = sg2d.transY; } else { p2df = new Path2D.Float(s, sg2d.transform); transx = 0; transy = 0; } drawPath(sg2d, p2df, transx, transy); } else if (sg2d.strokeState < SunGraphics2D.STROKE_CUSTOM) { ShapeSpanIterator si = LoopPipe.getStrokeSpans(sg2d, s); try { fillSpans(sg2d, si, 0, 0); } finally { si.dispose(); } } else { fill(sg2d, sg2d.stroke.createStrokedShape(s)); } } public void fill(SunGraphics2D sg2d, Shape s) { int transx, transy; if (sg2d.strokeState == SunGraphics2D.STROKE_THIN) { // Here we are able to use fillPath() for // high-quality fills. Path2D.Float p2df; if (sg2d.transformState <= SunGraphics2D.TRANSFORM_INT_TRANSLATE) { if (s instanceof Path2D.Float) { p2df = (Path2D.Float)s; } else { p2df = new Path2D.Float(s); } transx = sg2d.transX; transy = sg2d.transY; } else { p2df = new Path2D.Float(s, sg2d.transform); transx = 0; transy = 0; } fillPath(sg2d, p2df, transx, transy); return; } AffineTransform at; if (sg2d.transformState <= SunGraphics2D.TRANSFORM_INT_TRANSLATE) { // Transform (translation) will be done by FillSpans (we could // delegate to fillPolygon() here, but most hardware accelerated // libraries cannot handle non-convex polygons, so we will use // the FillSpans approach by default) at = null; transx = sg2d.transX; transy = sg2d.transY; } else { // Transform will be done by the PathIterator at = sg2d.transform; transx = transy = 0; } ShapeSpanIterator ssi = LoopPipe.getFillSSI(sg2d); try { // Subtract transx/y from the SSI clip to match the // (potentially untranslated) geometry fed to it Region clip = sg2d.getCompClip(); ssi.setOutputAreaXYXY(clip.getLoX() - transx, clip.getLoY() - transy, clip.getHiX() - transx, clip.getHiY() - transy); ssi.appendPath(s.getPathIterator(at)); fillSpans(sg2d, ssi, transx, transy); } finally { ssi.dispose(); } } }