J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved. |
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| 4 | * |
| 5 | * This code is free software; you can redistribute it and/or modify it |
| 6 | * under the terms of the GNU General Public License version 2 only, as |
| 7 | * published by the Free Software Foundation. Sun designates this |
| 8 | * particular file as subject to the "Classpath" exception as provided |
| 9 | * by Sun in the LICENSE file that accompanied this code. |
| 10 | * |
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 14 | * version 2 for more details (a copy is included in the LICENSE file that |
| 15 | * accompanied this code). |
| 16 | * |
| 17 | * You should have received a copy of the GNU General Public License version |
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, |
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| 20 | * |
| 21 | * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| 22 | * CA 95054 USA or visit www.sun.com if you need additional information or |
| 23 | * have any questions. |
| 24 | */ |
| 25 | |
| 26 | package sun.java2d.pipe; |
| 27 | |
| 28 | import java.awt.Polygon; |
| 29 | import java.awt.Shape; |
| 30 | import java.awt.geom.AffineTransform; |
| 31 | import java.awt.geom.Arc2D; |
| 32 | import java.awt.geom.Ellipse2D; |
| 33 | import java.awt.geom.Path2D; |
| 34 | import java.awt.geom.IllegalPathStateException; |
| 35 | import java.awt.geom.PathIterator; |
| 36 | import java.awt.geom.RoundRectangle2D; |
| 37 | import sun.java2d.SunGraphics2D; |
| 38 | import sun.java2d.loops.ProcessPath; |
| 39 | import static sun.java2d.pipe.BufferedOpCodes.*; |
| 40 | |
| 41 | /** |
| 42 | * Base class for enqueuing rendering operations in a single-threaded |
| 43 | * rendering environment. Instead of each operation being rendered |
| 44 | * immediately by the underlying graphics library, the operation will be |
| 45 | * added to the provided RenderQueue, which will be processed at a later |
| 46 | * time by a single thread. |
| 47 | * |
| 48 | * This class provides implementations of drawLine(), drawRect(), drawPoly(), |
| 49 | * fillRect(), draw(Shape), and fill(Shape), which are useful for a |
| 50 | * hardware-accelerated renderer. The other draw*() and fill*() methods |
| 51 | * simply delegate to draw(Shape) and fill(Shape), respectively. |
| 52 | */ |
| 53 | public abstract class BufferedRenderPipe |
| 54 | implements PixelDrawPipe, PixelFillPipe, ShapeDrawPipe |
| 55 | { |
| 56 | static final int BYTES_PER_POLY_POINT = 8; |
| 57 | static final int BYTES_PER_SCANLINE = 12; |
| 58 | static final int BYTES_PER_SPAN = 16; |
| 59 | |
| 60 | protected RenderQueue rq; |
| 61 | protected RenderBuffer buf; |
| 62 | private BufferedDrawHandler drawHandler; |
| 63 | |
| 64 | public BufferedRenderPipe(RenderQueue rq) { |
| 65 | this.rq = rq; |
| 66 | this.buf = rq.getBuffer(); |
| 67 | this.drawHandler = new BufferedDrawHandler(); |
| 68 | } |
| 69 | |
| 70 | /** |
| 71 | * Validates the state in the provided SunGraphics2D object and sets up |
| 72 | * any special resources for this operation (e.g. enabling gradient |
| 73 | * shading). |
| 74 | */ |
| 75 | protected abstract void validateContext(SunGraphics2D sg2d); |
| 76 | |
| 77 | public void drawLine(SunGraphics2D sg2d, |
| 78 | int x1, int y1, int x2, int y2) |
| 79 | { |
| 80 | int transx = sg2d.transX; |
| 81 | int transy = sg2d.transY; |
| 82 | rq.lock(); |
| 83 | try { |
| 84 | validateContext(sg2d); |
| 85 | rq.ensureCapacity(20); |
| 86 | buf.putInt(DRAW_LINE); |
| 87 | buf.putInt(x1 + transx); |
| 88 | buf.putInt(y1 + transy); |
| 89 | buf.putInt(x2 + transx); |
| 90 | buf.putInt(y2 + transy); |
| 91 | } finally { |
| 92 | rq.unlock(); |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | public void drawRect(SunGraphics2D sg2d, |
| 97 | int x, int y, int width, int height) |
| 98 | { |
| 99 | rq.lock(); |
| 100 | try { |
| 101 | validateContext(sg2d); |
| 102 | rq.ensureCapacity(20); |
| 103 | buf.putInt(DRAW_RECT); |
| 104 | buf.putInt(x + sg2d.transX); |
| 105 | buf.putInt(y + sg2d.transY); |
| 106 | buf.putInt(width); |
| 107 | buf.putInt(height); |
| 108 | } finally { |
| 109 | rq.unlock(); |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | public void fillRect(SunGraphics2D sg2d, |
| 114 | int x, int y, int width, int height) |
| 115 | { |
| 116 | rq.lock(); |
| 117 | try { |
| 118 | validateContext(sg2d); |
| 119 | rq.ensureCapacity(20); |
| 120 | buf.putInt(FILL_RECT); |
| 121 | buf.putInt(x + sg2d.transX); |
| 122 | buf.putInt(y + sg2d.transY); |
| 123 | buf.putInt(width); |
| 124 | buf.putInt(height); |
| 125 | } finally { |
| 126 | rq.unlock(); |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | public void drawRoundRect(SunGraphics2D sg2d, |
| 131 | int x, int y, int width, int height, |
| 132 | int arcWidth, int arcHeight) |
| 133 | { |
| 134 | draw(sg2d, new RoundRectangle2D.Float(x, y, width, height, |
| 135 | arcWidth, arcHeight)); |
| 136 | } |
| 137 | |
| 138 | public void fillRoundRect(SunGraphics2D sg2d, |
| 139 | int x, int y, int width, int height, |
| 140 | int arcWidth, int arcHeight) |
| 141 | { |
| 142 | fill(sg2d, new RoundRectangle2D.Float(x, y, width, height, |
| 143 | arcWidth, arcHeight)); |
| 144 | } |
| 145 | |
| 146 | public void drawOval(SunGraphics2D sg2d, |
| 147 | int x, int y, int width, int height) |
| 148 | { |
| 149 | draw(sg2d, new Ellipse2D.Float(x, y, width, height)); |
| 150 | } |
| 151 | |
| 152 | public void fillOval(SunGraphics2D sg2d, |
| 153 | int x, int y, int width, int height) |
| 154 | { |
| 155 | fill(sg2d, new Ellipse2D.Float(x, y, width, height)); |
| 156 | } |
| 157 | |
| 158 | public void drawArc(SunGraphics2D sg2d, |
| 159 | int x, int y, int width, int height, |
| 160 | int startAngle, int arcAngle) |
| 161 | { |
| 162 | draw(sg2d, new Arc2D.Float(x, y, width, height, |
| 163 | startAngle, arcAngle, |
| 164 | Arc2D.OPEN)); |
| 165 | } |
| 166 | |
| 167 | public void fillArc(SunGraphics2D sg2d, |
| 168 | int x, int y, int width, int height, |
| 169 | int startAngle, int arcAngle) |
| 170 | { |
| 171 | fill(sg2d, new Arc2D.Float(x, y, width, height, |
| 172 | startAngle, arcAngle, |
| 173 | Arc2D.PIE)); |
| 174 | } |
| 175 | |
| 176 | protected void drawPoly(final SunGraphics2D sg2d, |
| 177 | final int[] xPoints, final int[] yPoints, |
| 178 | final int nPoints, final boolean isClosed) |
| 179 | { |
| 180 | if (xPoints == null || yPoints == null) { |
| 181 | throw new NullPointerException("coordinate array"); |
| 182 | } |
| 183 | if (xPoints.length < nPoints || yPoints.length < nPoints) { |
| 184 | throw new ArrayIndexOutOfBoundsException("coordinate array"); |
| 185 | } |
| 186 | |
| 187 | if (nPoints < 2) { |
| 188 | // render nothing |
| 189 | return; |
| 190 | } else if (nPoints == 2 && !isClosed) { |
| 191 | // render a simple line |
| 192 | drawLine(sg2d, xPoints[0], yPoints[0], xPoints[1], yPoints[1]); |
| 193 | return; |
| 194 | } |
| 195 | |
| 196 | rq.lock(); |
| 197 | try { |
| 198 | validateContext(sg2d); |
| 199 | |
| 200 | int pointBytesRequired = nPoints * BYTES_PER_POLY_POINT; |
| 201 | int totalBytesRequired = 20 + pointBytesRequired; |
| 202 | |
| 203 | if (totalBytesRequired <= buf.capacity()) { |
| 204 | if (totalBytesRequired > buf.remaining()) { |
| 205 | // process the queue first and then enqueue the points |
| 206 | rq.flushNow(); |
| 207 | } |
| 208 | buf.putInt(DRAW_POLY); |
| 209 | // enqueue parameters |
| 210 | buf.putInt(nPoints); |
| 211 | buf.putInt(isClosed ? 1 : 0); |
| 212 | buf.putInt(sg2d.transX); |
| 213 | buf.putInt(sg2d.transY); |
| 214 | // enqueue the points |
| 215 | buf.put(xPoints, 0, nPoints); |
| 216 | buf.put(yPoints, 0, nPoints); |
| 217 | } else { |
| 218 | // queue is too small to accomodate all points; perform the |
| 219 | // operation directly on the queue flushing thread |
| 220 | rq.flushAndInvokeNow(new Runnable() { |
| 221 | public void run() { |
| 222 | drawPoly(xPoints, yPoints, |
| 223 | nPoints, isClosed, |
| 224 | sg2d.transX, sg2d.transY); |
| 225 | } |
| 226 | }); |
| 227 | } |
| 228 | } finally { |
| 229 | rq.unlock(); |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | protected abstract void drawPoly(int[] xPoints, int[] yPoints, |
| 234 | int nPoints, boolean isClosed, |
| 235 | int transX, int transY); |
| 236 | |
| 237 | public void drawPolyline(SunGraphics2D sg2d, |
| 238 | int[] xPoints, int[] yPoints, |
| 239 | int nPoints) |
| 240 | { |
| 241 | drawPoly(sg2d, xPoints, yPoints, nPoints, false); |
| 242 | } |
| 243 | |
| 244 | public void drawPolygon(SunGraphics2D sg2d, |
| 245 | int[] xPoints, int[] yPoints, |
| 246 | int nPoints) |
| 247 | { |
| 248 | drawPoly(sg2d, xPoints, yPoints, nPoints, true); |
| 249 | } |
| 250 | |
| 251 | public void fillPolygon(SunGraphics2D sg2d, |
| 252 | int[] xPoints, int[] yPoints, |
| 253 | int nPoints) |
| 254 | { |
| 255 | fill(sg2d, new Polygon(xPoints, yPoints, nPoints)); |
| 256 | } |
| 257 | |
| 258 | private class BufferedDrawHandler |
| 259 | extends ProcessPath.DrawHandler |
| 260 | { |
| 261 | BufferedDrawHandler() { |
| 262 | // these are bogus values; the caller will use validate() |
| 263 | // to ensure that they are set properly prior to each usage |
| 264 | super(0, 0, 0, 0); |
| 265 | } |
| 266 | |
| 267 | /** |
| 268 | * This method needs to be called prior to each draw/fillPath() |
| 269 | * operation to ensure the clip bounds are up to date. |
| 270 | */ |
| 271 | void validate(SunGraphics2D sg2d) { |
| 272 | Region clip = sg2d.getCompClip(); |
| 273 | setBounds(clip.getLoX(), clip.getLoY(), |
| 274 | clip.getHiX(), clip.getHiY(), |
| 275 | sg2d.strokeHint); |
| 276 | } |
| 277 | |
| 278 | /** |
| 279 | * drawPath() support... |
| 280 | */ |
| 281 | |
| 282 | public void drawLine(int x1, int y1, int x2, int y2) { |
| 283 | // assert rq.lock.isHeldByCurrentThread(); |
| 284 | rq.ensureCapacity(20); |
| 285 | buf.putInt(DRAW_LINE); |
| 286 | buf.putInt(x1); |
| 287 | buf.putInt(y1); |
| 288 | buf.putInt(x2); |
| 289 | buf.putInt(y2); |
| 290 | } |
| 291 | |
| 292 | public void drawPixel(int x, int y) { |
| 293 | // assert rq.lock.isHeldByCurrentThread(); |
| 294 | rq.ensureCapacity(12); |
| 295 | buf.putInt(DRAW_PIXEL); |
| 296 | buf.putInt(x); |
| 297 | buf.putInt(y); |
| 298 | } |
| 299 | |
| 300 | /** |
| 301 | * fillPath() support... |
| 302 | */ |
| 303 | |
| 304 | private int scanlineCount; |
| 305 | private int scanlineCountIndex; |
| 306 | private int remainingScanlines; |
| 307 | |
| 308 | private void resetFillPath() { |
| 309 | buf.putInt(DRAW_SCANLINES); |
| 310 | scanlineCountIndex = buf.position(); |
| 311 | buf.putInt(0); |
| 312 | scanlineCount = 0; |
| 313 | remainingScanlines = buf.remaining() / BYTES_PER_SCANLINE; |
| 314 | } |
| 315 | |
| 316 | private void updateScanlineCount() { |
| 317 | buf.putInt(scanlineCountIndex, scanlineCount); |
| 318 | } |
| 319 | |
| 320 | /** |
| 321 | * Called from fillPath() to indicate that we are about to |
| 322 | * start issuing drawScanline() calls. |
| 323 | */ |
| 324 | public void startFillPath() { |
| 325 | rq.ensureCapacity(20); // to ensure room for at least a scanline |
| 326 | resetFillPath(); |
| 327 | } |
| 328 | |
| 329 | public void drawScanline(int x1, int x2, int y) { |
| 330 | if (remainingScanlines == 0) { |
| 331 | updateScanlineCount(); |
| 332 | rq.flushNow(); |
| 333 | resetFillPath(); |
| 334 | } |
| 335 | buf.putInt(x1); |
| 336 | buf.putInt(x2); |
| 337 | buf.putInt(y); |
| 338 | scanlineCount++; |
| 339 | remainingScanlines--; |
| 340 | } |
| 341 | |
| 342 | /** |
| 343 | * Called from fillPath() to indicate that we are done |
| 344 | * issuing drawScanline() calls. |
| 345 | */ |
| 346 | public void endFillPath() { |
| 347 | updateScanlineCount(); |
| 348 | } |
| 349 | } |
| 350 | |
| 351 | protected void drawPath(SunGraphics2D sg2d, |
| 352 | Path2D.Float p2df, int transx, int transy) |
| 353 | { |
| 354 | rq.lock(); |
| 355 | try { |
| 356 | validateContext(sg2d); |
| 357 | drawHandler.validate(sg2d); |
| 358 | ProcessPath.drawPath(drawHandler, p2df, transx, transy); |
| 359 | } finally { |
| 360 | rq.unlock(); |
| 361 | } |
| 362 | } |
| 363 | |
| 364 | protected void fillPath(SunGraphics2D sg2d, |
| 365 | Path2D.Float p2df, int transx, int transy) |
| 366 | { |
| 367 | rq.lock(); |
| 368 | try { |
| 369 | validateContext(sg2d); |
| 370 | drawHandler.validate(sg2d); |
| 371 | drawHandler.startFillPath(); |
| 372 | ProcessPath.fillPath(drawHandler, p2df, transx, transy); |
| 373 | drawHandler.endFillPath(); |
| 374 | } finally { |
| 375 | rq.unlock(); |
| 376 | } |
| 377 | } |
| 378 | |
| 379 | private native int fillSpans(RenderQueue rq, long buf, |
| 380 | int pos, int limit, |
| 381 | SpanIterator si, long iterator, |
| 382 | int transx, int transy); |
| 383 | |
| 384 | protected void fillSpans(SunGraphics2D sg2d, SpanIterator si, |
| 385 | int transx, int transy) |
| 386 | { |
| 387 | rq.lock(); |
| 388 | try { |
| 389 | validateContext(sg2d); |
| 390 | rq.ensureCapacity(24); // so that we have room for at least a span |
| 391 | int newpos = fillSpans(rq, buf.getAddress(), |
| 392 | buf.position(), buf.capacity(), |
| 393 | si, si.getNativeIterator(), |
| 394 | transx, transy); |
| 395 | buf.position(newpos); |
| 396 | } finally { |
| 397 | rq.unlock(); |
| 398 | } |
| 399 | } |
| 400 | |
| 401 | public void draw(SunGraphics2D sg2d, Shape s) { |
| 402 | if (sg2d.strokeState == sg2d.STROKE_THIN) { |
| 403 | if (s instanceof Polygon) { |
| 404 | if (sg2d.transformState < sg2d.TRANSFORM_TRANSLATESCALE) { |
| 405 | Polygon p = (Polygon)s; |
| 406 | drawPolygon(sg2d, p.xpoints, p.ypoints, p.npoints); |
| 407 | return; |
| 408 | } |
| 409 | } |
| 410 | Path2D.Float p2df; |
| 411 | int transx, transy; |
| 412 | if (sg2d.transformState <= sg2d.TRANSFORM_INT_TRANSLATE) { |
| 413 | if (s instanceof Path2D.Float) { |
| 414 | p2df = (Path2D.Float)s; |
| 415 | } else { |
| 416 | p2df = new Path2D.Float(s); |
| 417 | } |
| 418 | transx = sg2d.transX; |
| 419 | transy = sg2d.transY; |
| 420 | } else { |
| 421 | p2df = new Path2D.Float(s, sg2d.transform); |
| 422 | transx = 0; |
| 423 | transy = 0; |
| 424 | } |
| 425 | drawPath(sg2d, p2df, transx, transy); |
| 426 | } else if (sg2d.strokeState < sg2d.STROKE_CUSTOM) { |
| 427 | ShapeSpanIterator si = LoopPipe.getStrokeSpans(sg2d, s); |
| 428 | try { |
| 429 | fillSpans(sg2d, si, 0, 0); |
| 430 | } finally { |
| 431 | si.dispose(); |
| 432 | } |
| 433 | } else { |
| 434 | fill(sg2d, sg2d.stroke.createStrokedShape(s)); |
| 435 | } |
| 436 | } |
| 437 | |
| 438 | public void fill(SunGraphics2D sg2d, Shape s) { |
| 439 | int transx, transy; |
| 440 | |
| 441 | if (sg2d.strokeState == sg2d.STROKE_THIN) { |
| 442 | // Here we are able to use fillPath() for |
| 443 | // high-quality fills. |
| 444 | Path2D.Float p2df; |
| 445 | if (sg2d.transformState <= sg2d.TRANSFORM_INT_TRANSLATE) { |
| 446 | if (s instanceof Path2D.Float) { |
| 447 | p2df = (Path2D.Float)s; |
| 448 | } else { |
| 449 | p2df = new Path2D.Float(s); |
| 450 | } |
| 451 | transx = sg2d.transX; |
| 452 | transy = sg2d.transY; |
| 453 | } else { |
| 454 | p2df = new Path2D.Float(s, sg2d.transform); |
| 455 | transx = 0; |
| 456 | transy = 0; |
| 457 | } |
| 458 | fillPath(sg2d, p2df, transx, transy); |
| 459 | return; |
| 460 | } |
| 461 | |
| 462 | AffineTransform at; |
| 463 | if (sg2d.transformState <= sg2d.TRANSFORM_INT_TRANSLATE) { |
| 464 | // Transform (translation) will be done by FillSpans (we could |
| 465 | // delegate to fillPolygon() here, but most hardware accelerated |
| 466 | // libraries cannot handle non-convex polygons, so we will use |
| 467 | // the FillSpans approach by default) |
| 468 | at = null; |
| 469 | transx = sg2d.transX; |
| 470 | transy = sg2d.transY; |
| 471 | } else { |
| 472 | // Transform will be done by the PathIterator |
| 473 | at = sg2d.transform; |
| 474 | transx = transy = 0; |
| 475 | } |
| 476 | |
| 477 | ShapeSpanIterator ssi = LoopPipe.getFillSSI(sg2d); |
| 478 | try { |
| 479 | // Subtract transx/y from the SSI clip to match the |
| 480 | // (potentially untranslated) geometry fed to it |
| 481 | Region clip = sg2d.getCompClip(); |
| 482 | ssi.setOutputAreaXYXY(clip.getLoX() - transx, |
| 483 | clip.getLoY() - transy, |
| 484 | clip.getHiX() - transx, |
| 485 | clip.getHiY() - transy); |
| 486 | ssi.appendPath(s.getPathIterator(at)); |
| 487 | fillSpans(sg2d, ssi, transx, transy); |
| 488 | } finally { |
| 489 | ssi.dispose(); |
| 490 | } |
| 491 | } |
| 492 | } |