| /* |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package flash.graphics.g2d; |
| |
| import flash.swf.tags.DefineShape; |
| import flash.swf.tags.PlaceObject; |
| import flash.swf.tags.DefineSprite; |
| import flash.swf.tags.DefineBits; |
| import flash.swf.tags.DefineBitsLossless; |
| import flash.swf.builder.tags.DefineShapeBuilder; |
| import flash.swf.builder.tags.DefineBitsLosslessBuilder; |
| import flash.swf.builder.tags.DefineBitsBuilder; |
| import flash.swf.builder.tags.ImageShapeBuilder; |
| import flash.swf.builder.types.MatrixBuilder; |
| import flash.swf.types.TagList; |
| import flash.swf.types.Rect; |
| import flash.swf.types.Matrix; |
| import flash.swf.Tag; |
| import flash.graphics.images.LosslessImage; |
| import flash.graphics.images.JPEGImage; |
| |
| import java.awt.Shape; |
| import java.awt.Graphics; |
| import java.awt.Image; |
| import java.awt.Paint; |
| import java.awt.GradientPaint; |
| import java.awt.image.ImageObserver; |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.Point2D; |
| import java.awt.geom.Rectangle2D; |
| import java.io.InputStream; |
| import java.io.IOException; |
| |
| /** |
| * A SWF specific implementation of Java2D's <code>Graphics2D</code> |
| * API. Calls to this class are converted into a <code>TagList</code> |
| * that can be used to construct a SWF Sprite. |
| * |
| * @author Peter Farland |
| */ |
| public class SpriteGraphics2D extends AbstractGraphics2D |
| { |
| /** |
| * Constructor. |
| * Initializes a new GraphicContext and TagList. |
| * |
| * @param width |
| * @param height |
| */ |
| public SpriteGraphics2D(int width, int height) |
| { |
| super(new GraphicContext(width, height)); |
| this.width = width; |
| this.height = height; |
| init(); |
| } |
| |
| /** |
| * Optimized Constructor. |
| * Initializes a smaller GraphicContext and TagList, without FontMetrics support. |
| */ |
| public SpriteGraphics2D() |
| { |
| super(new GraphicContext()); |
| init(); |
| } |
| |
| private void init() |
| { |
| defineTags = new TagList(); |
| graphicContext.validateTransformStack(); |
| bounds = new Rect(); |
| } |
| |
| /** |
| * Private Constructor - used by create(). |
| * @param swf2d |
| * @see #create() |
| */ |
| private SpriteGraphics2D(SpriteGraphics2D swf2d) |
| { |
| super((GraphicContext)swf2d.graphicContext.clone()); |
| bounds = new Rect(); |
| defineTags = swf2d.getTags(); |
| } |
| |
| /** |
| * Draws only the outline of an AWT Shape in SWF vectors using the settings of the |
| * current graphics context and places it on the SWF display list. The current |
| * stroke and paint colors are used to render the shape. |
| * @param shape the shape to be drawn |
| * @see java.awt.Graphics2D#draw(java.awt.Shape) |
| */ |
| public void draw(Shape shape) |
| { |
| defineShape(shape, true, false); |
| } |
| |
| /** |
| * Draws only the center of a closed AWT shape in SWF vectors using the settings of the |
| * current graphic context and places it on the SWF display list. The current paint |
| * is used to render the shape. |
| * @param shape |
| * @see java.awt.Graphics2D#fill(java.awt.Shape) |
| */ |
| public void fill(Shape shape) |
| { |
| defineShape(shape, false, true); |
| } |
| |
| /** |
| * This method converts an AWT Shape into a SWF Shape and creates a DefineShape3 instance |
| * with an accompanying PlaceObject2 instance. |
| * <p> |
| * Two non-standard operations are performed to work around unavoidable issues. The first |
| * is to apply the current AffineTransform before rendering and moving the drawing pen |
| * origin to the first move command's location. The shape is correctly translated |
| * to its intended location on the client by using the PlaceObject2's matrix. |
| * </p> |
| * <p> |
| * The second issue is to deal with the incompatibility of SVGs viewport dimensions and |
| * SWF Sprite auto-scaling. We have to keep track of the minimum area required to contain |
| * the sprites manually, as using the viewport dimensions will produce unexpected scaling |
| * for developers. |
| * </p> |
| * @param shape |
| * @param draw |
| * @param fill |
| */ |
| private void defineShape(Shape shape, boolean draw, boolean fill) |
| { |
| Point2D oldPen = null; |
| boolean isGradient = false; |
| |
| if (fill && isGradientFill(graphicContext.getPaint())) |
| isGradient = true; |
| |
| if (isGradient) |
| { |
| shape = graphicContext.getTransform().createTransformedShape(shape); |
| oldPen = graphicContext.setPen(getShapeStart(shape)); |
| } |
| |
| DefineShapeBuilder builder = new DefineShapeBuilder(shape, graphicContext, draw, fill); |
| |
| DefineShape ds3 = (DefineShape)builder.build(); |
| defineTags.defineShape3(ds3); |
| |
| Matrix matrix; |
| |
| if (isGradient) |
| { |
| double originX = graphicContext.getPen().getX(); |
| double originY = graphicContext.getPen().getY(); |
| matrix = MatrixBuilder.getTranslateInstance(originX, originY); |
| } |
| else |
| { |
| matrix = MatrixBuilder.build(graphicContext.getTransform()); |
| } |
| |
| applyBounds(ds3.bounds.xMin + matrix.translateX, |
| ds3.bounds.yMin + matrix.translateY, |
| ds3.bounds.xMax + matrix.translateX, |
| ds3.bounds.yMax + matrix.translateY); |
| |
| PlaceObject po2 = new PlaceObject(Tag.stagPlaceObject2); |
| po2.setMatrix(matrix); |
| po2.setRef(ds3); |
| po2.depth = depth++; |
| |
| if (isGradient) |
| graphicContext.setPen(oldPen); |
| |
| defineTags.placeObject2(po2); |
| } |
| |
| /** |
| * <b style="color:orange">NOT YET IMPLEMENTED AS DEFINED BELOW, CURRENTLY USING drawStringAsShape()</b><br /> |
| * Renders the text specified as a series of SWF character glyphs, |
| * using the current graphic context to control anti-aliasing and color. |
| * Unique glyphs are stored in a SWF font definition, and the defined text |
| * references this by index. |
| * <p> |
| * The baseline of the first character is at position (<i>x</i>, <i>y</i>) |
| * in the User Space. |
| * </p> |
| * @param str the text to be rendered |
| * @param x baseline x co-ordinate |
| * @param y baseline y co-ordinate |
| * @see java.awt.Graphics2D#drawString(java.lang.String, float, float) |
| */ |
| public void drawString(String str, float x, float y) |
| { |
| drawStringAsShape(str, x, y); |
| } |
| |
| public void drawString(String str, int x, int y) |
| { |
| super.drawString(str, x, y); |
| } |
| |
| public boolean drawImage(Image image, AffineTransform at, ImageObserver obs) |
| { |
| if (image != null) |
| { |
| LosslessImage losslessImage = new LosslessImage(image); |
| int width = losslessImage.getWidth(); |
| int height = losslessImage.getHeight(); |
| DefineBitsLossless defineBits = DefineBitsLosslessBuilder.build(losslessImage.getPixels(), width, height); |
| defineTags.defineBitsLossless2(defineBits); |
| |
| DefineShape ds3 = ImageShapeBuilder.buildImage(defineBits, defineBits.width, defineBits.height); |
| defineTags.defineShape3(ds3); |
| applyBounds(ds3.bounds.xMin, ds3.bounds.yMin, ds3.bounds.xMax, ds3.bounds.yMax); |
| |
| PlaceObject po2 = new PlaceObject(Tag.stagPlaceObject2); |
| po2.setMatrix(MatrixBuilder.build(at)); |
| po2.setRef(ds3); |
| po2.depth = depth++; |
| |
| defineTags.placeObject2(po2); |
| } |
| |
| return false; |
| } |
| |
| public boolean drawImage(Image image, int x, int y, ImageObserver observer) |
| { |
| AffineTransform at = graphicContext.getTransform(); |
| at.translate(x, y); |
| return drawImage(image, at, observer); |
| } |
| |
| public boolean drawImage(Image image, int x, int y, int width, int height, ImageObserver observer) |
| { |
| AffineTransform at = graphicContext.getTransform(); |
| at.translate(x, y); |
| double sx = (double)width/(double)image.getWidth(observer); |
| double sy = (double)height/(double)image.getHeight(observer); |
| at.scale(sx, sy); |
| return drawImage(image, at, observer); |
| } |
| |
| /** |
| * Converts an AWT image into a SWF <code>DefineBitsLossless</code> tag and adds it to |
| * the Sprite <code>TagList</code> context. |
| * |
| * @param image - an AWT bitmapped image |
| * @param name - name to use in the corresponding <code>PlaceObject</code> tag |
| */ |
| public void drawNamedImage(Image image, String name) |
| { |
| if (image != null) |
| { |
| LosslessImage losslessImage = new LosslessImage(image); |
| int width = losslessImage.getWidth(); |
| int height = losslessImage.getHeight(); |
| DefineBitsLossless defineBits = DefineBitsLosslessBuilder.build(losslessImage.getPixels(), width, height); |
| defineTags.defineBitsLossless2(defineBits); |
| |
| DefineShape ds3 = ImageShapeBuilder.buildImage(defineBits, defineBits.width, defineBits.height); |
| defineTags.defineShape3(ds3); |
| applyBounds(ds3.bounds.xMin, ds3.bounds.yMin, ds3.bounds.xMax, ds3.bounds.yMax); |
| |
| PlaceObject po2 = new PlaceObject(ds3, depth++); |
| po2.setMatrix(MatrixBuilder.build(graphicContext.getTransform())); |
| po2.setName(name); |
| |
| defineTags.placeObject2(po2); |
| } |
| } |
| |
| |
| /** |
| * Converts a JPEG binary input stream into a SWF <code>DefineBits</code> tag and adds it |
| * to the Sprite <code>TagList</code> context. |
| * |
| * @param inputStream - the raw JPEG binary stream |
| * @param length - number of bits to read from the stream |
| * @param width - width of the JPEG image in pixels |
| * @param height - height of the JPEG image in pixels |
| * @param name - name to use in the corresponding <code>PlaceObject</code> tag |
| * @throws IOException |
| */ |
| public void drawJPEG(InputStream inputStream, int length, int width, int height, String name) throws IOException |
| { |
| if (inputStream != null) |
| { |
| try |
| { |
| JPEGImage image = new JPEGImage(inputStream, length); |
| DefineBits defineBits = new DefineBits(Tag.stagDefineBitsJPEG2); |
| defineBits.data = image.getData(); |
| defineTags.defineBitsJPEG2(defineBits); |
| |
| DefineShape ds3 = ImageShapeBuilder.buildImage(defineBits, width, height); |
| defineTags.defineShape3(ds3); |
| applyBounds(ds3.bounds.xMin, ds3.bounds.yMin, ds3.bounds.xMax, ds3.bounds.yMax); |
| |
| PlaceObject po2 = new PlaceObject(ds3, depth++); |
| po2.setMatrix(MatrixBuilder.build(graphicContext.getTransform())); |
| po2.setName(name); |
| |
| defineTags.placeObject2(po2); |
| } |
| finally |
| { |
| try |
| { |
| inputStream.close(); |
| } |
| catch (IOException e) |
| { |
| } |
| } |
| } |
| } |
| |
| /** |
| * Creates a new <code>Graphics</code> object that is a copy of |
| * this <code>SpriteGraphics2D</code> object. |
| * @return a copy of this Graphics2D instance |
| */ |
| public Graphics create() |
| { |
| return new SpriteGraphics2D(this); |
| } |
| |
| public TagList getTags() |
| { |
| return defineTags; |
| } |
| |
| public int getWidth() |
| { |
| return width; |
| } |
| |
| public int getHeight() |
| { |
| return height; |
| } |
| |
| public DefineSprite defineSprite(String name) |
| { |
| DefineSprite defineSprite = new DefineSprite(); |
| defineSprite.framecount = 1; |
| defineSprite.tagList = defineTags; |
| defineSprite.name = name; |
| return defineSprite; |
| } |
| |
| private static boolean isGradientFill(Paint paint) |
| { |
| return (paint != null |
| && (paint instanceof GradientPaint |
| || paint.getClass().getName().equals("org.apache.flex.forks.batik.ext.awt.MultipleGradientPaint"))); |
| } |
| |
| /** |
| * When trying to negate gradient dilution in filled SWF shapes, it is useful to |
| * know where the left most point of the physical shape as this can be used to change |
| * the drawing origin to minimize non-drawing move commands contributing to the |
| * shape bounds. |
| * |
| * @param shape |
| * @return Point2D the minimum x,y position of this shape's bounds. |
| */ |
| private static Point2D getShapeStart(Shape shape) |
| { |
| if (shape != null) |
| { |
| Rectangle2D bounds = shape.getBounds2D(); |
| return new Point2D.Double(bounds.getMinX(), bounds.getMinY()); |
| } |
| |
| return new Point2D.Double(0, 0); |
| } |
| |
| private void applyBounds(int x1, int y1, int x2, int y2) |
| { |
| if (x1 < bounds.xMin) bounds.xMin = x1; |
| if (y1 < bounds.yMin) bounds.yMin = y1; |
| if (x1 > bounds.xMax) bounds.xMax = x1; |
| if (y1 > bounds.yMax) bounds.yMax = y1; |
| if (x2 < bounds.xMin) bounds.xMin = x2; |
| if (y2 < bounds.yMin) bounds.yMin = y2; |
| if (x2 > bounds.xMax) bounds.xMax = x2; |
| if (y2 > bounds.yMax) bounds.yMax = y2; |
| } |
| |
| public Rect getBounds() |
| { |
| return bounds; |
| } |
| |
| protected TagList defineTags; |
| protected int depth = 1; |
| protected int width; |
| protected int height; |
| protected Rect bounds; |
| } |