| /* |
| * Copyright (c) 2005, 2008, 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 accomodate 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 == sg2d.STROKE_THIN) { |
| if (s instanceof Polygon) { |
| if (sg2d.transformState < sg2d.TRANSFORM_TRANSLATESCALE) { |
| Polygon p = (Polygon)s; |
| drawPolygon(sg2d, p.xpoints, p.ypoints, p.npoints); |
| return; |
| } |
| } |
| Path2D.Float p2df; |
| int transx, transy; |
| if (sg2d.transformState <= sg2d.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 < sg2d.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 == sg2d.STROKE_THIN) { |
| // Here we are able to use fillPath() for |
| // high-quality fills. |
| Path2D.Float p2df; |
| if (sg2d.transformState <= sg2d.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 <= sg2d.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(); |
| } |
| } |
| } |