001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026: package com.sun.perseus.j2d;
027:
028: import org.w3c.dom.svg.SVGPath;
029:
030: import com.sun.pisces.RendererBase;
031: import com.sun.pisces.PiscesRenderer;
032: import com.sun.pisces.Transform6;
033:
034: /**
035: * All rendering in Perseus is done through the <tt>RenderGraphics</tt> class.
036: * <br />
037: * <tt>RenderGraphics</tt> is the combination of the traditional
038: * <tt>Graphics2D</tt> API to the rendering engine and the notion of
039: * graphical context found in SVG.<br />
040: * <br />
041: * A <tt>RenderGraphics</tt> object proxies invocation to a <tt>Graphics2D</tt>
042: * instance through its <tt>draw</tt> or <tt>fill</tt> method while capturing
043: * the current rendering context state by implementing the
044: * <tt>RenderContext</tt> interface.
045: * <br />
046: * <b>Note A</b>: the Java 2D graphic context values passed by the
047: * <tt>RenderGraphics</tt> to the proxied <tt>Graphics2D</tt> correspond to
048: * the CSS 2 <a href="http://www.w3.org/TR/REC-CSS2/cascade.html#actual-value">
049: * actual</a> values.
050: * <br />
051: * <b>Note B</b>: the initial values for the context properties (such
052: * as <tt>color</tt> or <tt>fill</tt>) correspond to the CSS 2
053: * <a href="http://www.w3.org/TR/REC-CSS2/about.html#q7">
054: * initial</a> values for these properties.
055: *
056: * @see RenderContext
057: * @see java.awt.Graphics2D
058: *
059: */
060: public abstract class PiscesRenderGraphics extends RenderContext {
061: /**
062: * Constant used to handle setTransform(null)
063: * @see #setTransform
064: */
065: protected static final Transform IDENTITY = new Transform(null);
066:
067: /**
068: * The PaintTarget is the object defining the extent of the target rendered
069: * area. In some situations, this may be different than the primitive being
070: * drawn. For example, the same paint target may apply to multiple
071: * consecutive rendering calls.
072: */
073: protected PaintTarget paintTarget;
074:
075: /**
076: * The paintTransform defines the coordinate space into which the PaintDef
077: * should do its computation. Note that a PaintDef may add additional transform
078: * to the paintTransform, for example to account for objectBoundingBox paints
079: * or for paints which accept additional transforms (such as LinearGradientPaintDef
080: * which accepts a gradientTransform).
081: */
082: protected Transform paintTransform = null;
083:
084: /**
085: * The associated PiscesRenderer.
086: */
087: protected PiscesRenderer pr;
088:
089: /**
090: * The current transform.
091: */
092: protected Transform6 transform = new Transform6();
093:
094: /**
095: * Tracks whether or not the current transform needs to be set.
096: */
097: protected boolean needSetTransform = true;
098:
099: /**
100: * The image transform, used in drawImage
101: */
102: protected Transform6 imageTransform = new Transform6();
103:
104: /**
105: * The rendering extent along the x axis.
106: */
107: protected int width;
108:
109: /**
110: * The rendering extent along the y axis.
111: */
112: protected int height;
113:
114: /**
115: * The current rendering tile.
116: */
117: protected Tile renderingTile = new Tile();
118:
119: /**
120: * The current primitive tile, i.e., the one that should encompass the
121: * rendering of the following rendering primitive(s). This is used to
122: * account for round-off errors in bounds computation and cut-off rendering
123: * to the computed bouds for each primitive.
124: */
125: protected Tile primitiveTile = new Tile();
126:
127: /**
128: * Constructs a new <code>PiscesRenderGraphics</code> which will delegate painting
129: * operations to a <code>PiscesRenderer</code>.
130: *
131: * @param pr the <tt>PiscesRenderer</tt> to render to.
132: * @param width the rendering surface width. Should be greater than zero.
133: * @param height the rendering surface height. Should be greater than zero.
134: * @throws NullPointerException if bi is null
135: */
136: public PiscesRenderGraphics(final PiscesRenderer pr,
137: final int width, final int height) {
138: if (pr == null) {
139: throw new NullPointerException();
140: }
141:
142: if (width <= 0 || height <= 0) {
143: throw new IllegalArgumentException();
144: }
145:
146: this .width = width;
147: this .height = height;
148:
149: this .pr = pr;
150: setRenderingTile(null);
151: setPrimitiveTile(null);
152: }
153:
154: /**
155: * Clears the specified rectangle. IMPORTANT NOTE: the coordinates are in
156: * device space. This method does not account for the current transformation
157: * set on the RenderGraphics. It operates on the target pixels.
158: *
159: * @param x - the x coordinate of the rectangle to clear.
160: * @param y - the y coordinate of the rectangle to clear.
161: * @param width - the width of the rectangle to clear.
162: * @param height - the height of the rectangle to clear.
163: * @param clearColor - the color to use to clear the rectangle.
164: */
165: public abstract void clearRect(int x, int y, int width, int height,
166: RGB clearColor);
167:
168: /**
169: * Sets the current rendering tile to the rectangle specified by the given
170: * tile. IMPORTANT NOTE: the tile is _not_ subject to the RenderGraphics'
171: * transform. The clip is defined in device coordinates.
172: *
173: * @param renderingTile the Tile defining the clipping area. May be null.
174: */
175: public void setRenderingTile(final Tile renderingTile) {
176: if (renderingTile != null) {
177: this .renderingTile.x = renderingTile.x;
178: this .renderingTile.y = renderingTile.y;
179: this .renderingTile.maxX = renderingTile.maxX;
180: this .renderingTile.maxY = renderingTile.maxY;
181: } else {
182: this .renderingTile.x = 0;
183: this .renderingTile.y = 0;
184: this .renderingTile.maxX = width - 1;
185: this .renderingTile.maxY = height - 1;
186: }
187:
188: setPrimitiveTile(renderingTile);
189: }
190:
191: /**
192: * Sets the primitive tile, which is intersected with the rendering tile.
193: *
194: * @param primitiveTile if null, this sets the primitiveTile to the same value
195: * as the renderingTile
196: */
197: public void setPrimitiveTile(final Tile primitiveTile) {
198: if (primitiveTile != null) {
199: this .primitiveTile.x = primitiveTile.x;
200: this .primitiveTile.y = primitiveTile.y;
201: this .primitiveTile.maxX = primitiveTile.maxX;
202: this .primitiveTile.maxY = primitiveTile.maxY;
203: } else {
204: this .primitiveTile.x = renderingTile.x;
205: this .primitiveTile.y = renderingTile.y;
206: this .primitiveTile.maxX = renderingTile.maxX;
207: this .primitiveTile.maxY = renderingTile.maxY;
208: }
209:
210: applyClip();
211: }
212:
213: /**
214: * Applies the intersection of the rendering tile and the primitive tile
215: */
216: void applyClip() {
217: int x = primitiveTile.x;
218: int y = primitiveTile.y;
219: int mx = primitiveTile.maxX;
220: int my = primitiveTile.maxY;
221:
222: if (x < renderingTile.x) {
223: x = renderingTile.x;
224: }
225: if (y < renderingTile.y) {
226: y = renderingTile.y;
227: }
228: if (mx > renderingTile.maxX) {
229: mx = renderingTile.maxX;
230: }
231: if (my > renderingTile.maxY) {
232: my = renderingTile.maxY;
233: }
234:
235: final int w = mx - x + 1;
236: final int h = my - y + 1;
237: if (w <= 0 || h <= 0) {
238: throw new IllegalArgumentException();
239: }
240:
241: pr.setClip(x, y, w, h);
242: }
243:
244: /**
245: * @return the current rendering tile. This value is _never_ null.
246: */
247: public Tile getRenderingTile() {
248: return renderingTile;
249: }
250:
251: /**
252: * @return the current user clip tile. This value is _never_ null.
253: */
254: public Tile getPrimitiveTile() {
255: return primitiveTile;
256: }
257:
258: /**
259: * Sets the current PaintTarget.
260: *
261: * @param paintTarget the new PaintTarget.
262: */
263: public void setPaintTarget(final PaintTarget paintTarget) {
264: this .paintTarget = paintTarget;
265: }
266:
267: /**
268: * Sets the current paintTransform.
269: *
270: * @param paintTransform the new paintTransform.
271: */
272: public void setPaintTransform(final Transform paintTransform) {
273: this .paintTransform = paintTransform;
274: }
275:
276: /**
277: * Turns the high quality rendering on or off.
278: *
279: * @param isHigh true if the rendering quality should be high.
280: */
281: public void setRenderingQuality(boolean isHigh) {
282: if (isHigh) {
283: pr.setAntialiasing(true);
284: } else {
285: pr.setAntialiasing(false);
286: }
287: }
288:
289: /**
290: * Setting the transform to null is equivalent to setting it
291: * to identity.
292: * @param newTransform the new value of the transform. This value is
293: * copied, not referenced by the context.
294: */
295: public void setTransform(final Transform newTransform) {
296: needSetTransform = (setTransform(newTransform, transform) || needSetTransform);
297: needSetTransform = true;
298: }
299:
300: /**
301: * Transfers the transform values from the input Perseus Transform
302: * the the input Pisces Transform6.
303: *
304: * @param newTransform the Perseus transform to transfer.
305: * @param outTransform the Pisces transform destination.
306: * @return true if the transform was indeed different.
307: */
308: static boolean setTransform(final Transform newTransform,
309: final Transform6 transform) {
310: if (newTransform == null) {
311: return setTransform(IDENTITY, transform);
312: }
313:
314: int m00 = (int) (newTransform.m0 * 65536);
315: int m10 = (int) (newTransform.m1 * 65536);
316: int m01 = (int) (newTransform.m2 * 65536);
317: int m11 = (int) (newTransform.m3 * 65536);
318: int m02 = (int) (newTransform.m4 * 65536);
319: int m12 = (int) (newTransform.m5 * 65536);
320:
321: // Check if new value is actually different from current
322: // transform setting.
323: if (m00 != transform.m00 || m10 != transform.m10
324: || m01 != transform.m01 || m11 != transform.m11
325: || m02 != transform.m02 || m12 != transform.m12) {
326: // There is a change
327: transform.m00 = m00;
328: transform.m10 = m10;
329: transform.m01 = m01;
330: transform.m11 = m11;
331: transform.m02 = m02;
332: transform.m12 = m12;
333: return true;
334: }
335:
336: return false;
337: }
338:
339: /**
340: * fills the input shape with the current fill color.
341: *
342: * @param path the <tt>Path</tt> to fill
343: */
344: public void fill(final Path path) {
345: fillOrDraw(path, fill, getFillOpacityImpl(), true);
346: }
347:
348: /**
349: * @param path the Path to fill or draw.
350: * @param paint the paint to use for the operation.
351: * @param opOpacity the opacity to use for the operation.
352: * @param isFill if true, this is a fill operation. Otherwise, it is
353: * a stroke operation.
354: */
355: void fillOrDraw(final Path path, final PaintServer paint,
356: final int opOpacity, final boolean isFill) {
357: if (needSetTransform) {
358: pr.setTransform(transform);
359: needSetTransform = false;
360: }
361:
362: paint.getPaintDef().setPaint(this , pr, opOpacity);
363: if (isFill) {
364: pr.setFill();
365: pr.beginRendering(getFillRule());
366: } else {
367: pr.setStroke(strokeWidth, getStrokeLineCap(),
368: getStrokeLineJoin(), strokeMiterLimit,
369: strokeDashArray, computeStrokeDashOffset());
370: pr.beginRendering(WIND_NON_ZERO);
371: }
372:
373: pr.setPathData(path.data, path.commands, path.nSegments);
374:
375: pr.endRendering();
376: }
377:
378: /**
379: * Draws the input shape using a stroke
380: * derived from the following properties:
381: * <ul>
382: * <li>strokeWidth</li>
383: * <li>strokeDashArray</li>
384: * <li>strokeDashOffset</li>
385: * <li>strokeLineJoin</li>
386: * <li>strokeLineCap</li>
387: * </ul>
388: *
389: * @param path the <tt>Path</tt> to fill
390: */
391: public void draw(final Path path) {
392: fillOrDraw(path, stroke, getStrokeOpacityImpl(), false);
393: }
394:
395: /**
396: * Fills a rectangle.
397: *
398: * @param x the rectangle's x-axis origin
399: * @param y the rectangle's y-axis origin
400: * @param w the rectangle's length along the x-axis
401: * @param h the rectangle's length along the y-axis
402: * @param aw the rectangle's rounded corner diameter along the x-axis
403: * @param ah the rectangle's rounded corner diameter along the y-axis.
404: */
405: public void fillRect(float x, float y, float w, float h, float aw,
406: float ah) {
407: if (needSetTransform) {
408: pr.setTransform(transform);
409: needSetTransform = false;
410: }
411:
412: fill.getPaintDef().setPaint(this , pr, getFillOpacityImpl());
413:
414: if (aw > 0 || ah > 0) {
415: pr.fillRoundRect((int) (x * 65536), (int) (y * 65536),
416: (int) (w * 65536), (int) (h * 65536),
417: (int) (aw * 65536), (int) (ah * 65536));
418: } else {
419: pr.fillRect((int) (x * 65536), (int) (y * 65536),
420: (int) (w * 65536), (int) (h * 65536));
421: }
422: }
423:
424: /*
425: * Draws a rectangle.
426: *
427: * @param x the rectangle's x-axis origin
428: * @param y the rectangle's y-axis origin
429: * @param w the rectangle's length along the x-axis
430: * @param h the rectangle's length along the y-axis
431: * @param aw the rectangle's rounded corner diameter along the x-axis
432: * @param ah the rectangle's rounded corner diameter along the y-axis.
433: * @param rc the RenderContext defining rendering conditions.
434: */
435: public void drawRect(float x, float y, float w, float h, float aw,
436: float ah) {
437: if (needSetTransform) {
438: pr.setTransform(transform);
439: needSetTransform = false;
440: }
441:
442: stroke.getPaintDef().setPaint(this , pr, getStrokeOpacityImpl());
443: pr.setStroke(strokeWidth, getStrokeLineCap(),
444: getStrokeLineJoin(), strokeMiterLimit, strokeDashArray,
445: computeStrokeDashOffset());
446:
447: if (aw > 0 || ah > 0) {
448: pr.drawRoundRect((int) (x * 65536), (int) (y * 65536),
449: (int) (w * 65536), (int) (h * 65536),
450: (int) (aw * 65536), (int) (ah * 65536));
451:
452: } else {
453:
454: pr.drawRect((int) (x * 65536), (int) (y * 65536),
455: (int) (w * 65536), (int) (h * 65536));
456: }
457: }
458:
459: /*
460: * @param x the ellipse's x-axis origin
461: * @param y the ellipse's y-axis origin
462: * @param width the ellipse's x-axis length
463: * @param height the ellipse's y-axis length.
464: */
465: public void drawOval(float x, float y, float w, float h) {
466: if (needSetTransform) {
467: pr.setTransform(transform);
468: needSetTransform = false;
469: }
470:
471: stroke.getPaintDef().setPaint(this , pr, getStrokeOpacityImpl());
472:
473: pr.setStroke(strokeWidth, getStrokeLineCap(),
474: getStrokeLineJoin(), strokeMiterLimit, strokeDashArray,
475: computeStrokeDashOffset());
476:
477: pr.drawOval((int) (x * 65536), (int) (y * 65536),
478: (int) (w * 65536), (int) (h * 65536));
479: }
480:
481: /*
482: * @param x the ellipse's x-axis origin
483: * @param y the ellipse's y-axis origin
484: * @param width the ellipse's x-axis length
485: * @param height the ellipse's y-axis length.
486: */
487: public void fillOval(float x, float y, float w, float h) {
488: if (needSetTransform) {
489: pr.setTransform(transform);
490: needSetTransform = false;
491: }
492:
493: fill.getPaintDef().setPaint(this , pr, getFillOpacityImpl());
494: pr.fillOval((int) (x * 65536), (int) (y * 65536),
495: (int) (w * 65536), (int) (h * 65536));
496: }
497:
498: /**
499: * @param x1 the line's x-axis starting position.
500: * @param y1 the line's y-axis starting position.
501: * @param x2 the line's x-axis end position.
502: * @param y2 the line's y-axis end position.
503: */
504: public void drawLine(float x1, float y1, float x2, float y2) {
505: if (needSetTransform) {
506: pr.setTransform(transform);
507: needSetTransform = false;
508: }
509:
510: stroke.getPaintDef().setPaint(this , pr, getStrokeOpacityImpl());
511:
512: pr.setStroke(strokeWidth, getStrokeLineCap(),
513: getStrokeLineJoin(), strokeMiterLimit, strokeDashArray,
514: computeStrokeDashOffset());
515:
516: pr.drawLine((int) (x1 * 65536), (int) (y1 * 65536),
517: (int) (x2 * 65536), (int) (y2 * 65536));
518: }
519:
520: /**
521: * Draws the input Image at the specified location applying the
522: * input transform to the image before drawing it onto the
523: * proxied <tt>Graphics2D</tt>
524: *
525: * @param image the <tt>Image</tt> to draw
526: * @param dx the coordinate, along the x-axis, where the image should be drawn, in
527: * user space.
528: * @param dy the coordinate, along the y-axis, where the image should be drawn, in
529: * user space.
530: * @param dw the width, in the destination user space, of the image when drawn.
531: * @param dh the height, in the destination user space, of the image when drawn.
532: */
533: public void drawImage(final RasterImage image, float dx, float dy,
534: float dw, float dh) {
535: // Don't process degenerate cases.
536: if (image == null || image.getWidth() <= 0
537: || image.getHeight() <= 0 || dw <= 0 || dh <= 0) {
538: return;
539: }
540:
541: int sw = image.getWidth();
542: int sh = image.getHeight();
543:
544: if (needSetTransform) {
545: pr.setTransform(transform);
546: needSetTransform = false;
547: }
548:
549: // We compute the transform so that the rectangle (0, 0, sw, sh) is
550: // mapped to (dx, dy, dw, dh).
551: float scaleX = dw / sw;
552: float scaleY = dh / sh;
553:
554: Transform6 imageTransform = new Transform6();
555:
556: imageTransform.m00 = (int) (scaleX * 65536.0f);
557: imageTransform.m11 = (int) (scaleY * 65536.0f);
558: imageTransform.m02 = (int) (dx * 65536.0f);
559: imageTransform.m12 = (int) (dy * 65536.0f);
560:
561: if (getOpacity() != 0.0f) {
562:
563: pr.setTextureOpacity(getOpacity());
564:
565: pr.setTexture(RendererBase.TYPE_INT_RGB, image.getRGB(),
566: sw, sh, 0, sw, imageTransform, false);
567:
568: pr.fillRect((int) (dx * 65536), (int) (dy * 65536),
569: (int) (dw * 65536), (int) (dh * 65536));
570: }
571: }
572: }
|