/* * * 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 org.apache.flex.compiler.internal.fxg.swf; import java.awt.Image; import java.awt.Toolkit; import java.awt.image.ImageObserver; import java.awt.image.PixelGrabber; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import org.apache.flex.compiler.fxg.FXGVersion; import org.apache.flex.compiler.internal.embedding.transcoders.JPEGTranscoder; import org.apache.flex.compiler.internal.fxg.dom.AbstractFXGNode; import org.apache.flex.compiler.internal.fxg.dom.BitmapGraphicNode; import org.apache.flex.compiler.internal.fxg.dom.IFillNode; import org.apache.flex.compiler.internal.fxg.dom.fills.BitmapFillNode; import org.apache.flex.compiler.internal.fxg.dom.types.FillMode; import org.apache.flex.swf.ISWFConstants; import org.apache.flex.swf.tags.DefineBitsJPEG2Tag; import org.apache.flex.swf.tags.DefineBitsLossless2Tag; import org.apache.flex.swf.tags.DefineBitsLosslessTag; import org.apache.flex.swf.tags.DefineShape4Tag; import org.apache.flex.swf.tags.DefineShapeTag; import org.apache.flex.swf.types.FillStyle; import org.apache.flex.swf.types.FillStyleArray; import org.apache.flex.swf.types.LineStyleArray; import org.apache.flex.swf.types.Matrix; import org.apache.flex.swf.types.Rect; import org.apache.flex.swf.types.ShapeRecord; import org.apache.flex.swf.types.ShapeWithStyle; import org.apache.flex.swf.types.StraightEdgeRecord; import org.apache.flex.swf.types.StyleChangeRecord; import org.apache.flex.swf.types.Styles; import org.apache.flex.utils.FileUtils; import org.apache.flex.utils.Trace; /** * Utilities to help create SWF DefineBits and DefineBitsLossess image tags. */ public class ImageHelper { public static final String MIME_GIF = "image/gif"; public static final String MIME_JPEG = "image/jpeg"; public static final String MIME_JPG = "image/jpg"; public static final String MIME_PNG = "image/png"; /** * Creates a rectangle for the given width and height as a DefineShape. The * shape is painted with a bitmap FillStyle with the given DefineBits * tag. * * @param image The DefineBits tag encoding the image. * @param node The BitmapGraphicNode. * @return A rectangle of given width and height as a DefineShape with a * bitmap fill. */ public static DefineShapeTag createShapeForImage(DefineImage image, BitmapGraphicNode node) { double width = node.width; double height = node.height; boolean repeat = node.repeat; FillMode fillMode = node.fillMode; FXGVersion fileVersion = node.getFileVersion(); // Use default width/height information if none specified if (Double.isNaN(width)) width = image.getWidth(); if (Double.isNaN(height)) height = image.getHeight(); // Create Fill Style Matrix matrix = new Matrix(); double twx = (ISWFConstants.TWIPS_PER_PIXEL); matrix.setScale(twx, twx); FillStyle fs = null; if (fileVersion.equalTo(FXGVersion.v1_0)) { if (repeat) fs = new FillStyle(FillStyle.REPEATING_BITMAP_FILL, matrix, image.getTag()); else fs = new FillStyle(FillStyle.CLIPPED_BITMAP_FILL, matrix, image.getTag()); } else { if (fillMode.equals(FillMode.REPEAT)) { fs = new FillStyle(FillStyle.REPEATING_BITMAP_FILL, matrix, image.getTag()); } else if (fillMode.equals(FillMode.CLIP)) { fs = new FillStyle(FillStyle.CLIPPED_BITMAP_FILL, matrix, image.getTag()); } else if (fillMode.equals(FillMode.SCALE)) { //override the scale for matrix double fwidth = (width*ISWFConstants.TWIPS_PER_PIXEL)/(double)image.getWidth(); double fheight = (height*ISWFConstants.TWIPS_PER_PIXEL)/(double)image.getHeight(); //For consistency with the 4.5.1 snapshot of the flex compiler. fwidth = ((double)StrictMath.rint(0x10000 * fwidth))/((double)0x10000); fheight = ((double)StrictMath.rint(0x10000 * fheight))/((double)0x10000); matrix.setScale(fwidth, fheight); //fill style does not matter much since the entire area is filled with bitmap fs = new FillStyle(FillStyle.CLIPPED_BITMAP_FILL, matrix, image.getTag()); } } // Apply Fill Styles FillStyleArray styleArray = new FillStyleArray(); styleArray.add(fs); LineStyleArray lineStyleArray = new LineStyleArray(); Styles styles = new Styles(styleArray, lineStyleArray); ShapeWithStyle sws = new ShapeWithStyle(styles); // Build Raw SWF Shape List<ShapeRecord> shapeRecords = ShapeHelper.rectangle(width, height); ShapeHelper.setStyles(shapeRecords, 0, 1, 0, styles); sws.addShapeRecords(shapeRecords); // Wrap up into a SWF DefineShape Tag DefineShape4Tag defineShape = new DefineShape4Tag(); defineShape.setShapeBounds(TypeHelper.rect(width, height)); defineShape.setEdgeBounds(defineShape.getShapeBounds()); defineShape.setShapes(sws); return defineShape; } /** * Determines whether the bitmap image should be clipped. * * @param defImage The tag encoding the image. * @param node The BitmapGraphicNode. * @return boolean if bitmap should be clipped. */ public static boolean bitmapImageNeedsClipping(DefineImage defImage, BitmapGraphicNode node) { if (((node.getFileVersion().equalTo(FXGVersion.v1_0)) && !node.repeat) || (node.fillMode.equals(FillMode.CLIP))) { if ((defImage.getWidth() < node.width) || (defImage.getHeight() < node.height)) return true; } return false; } /** * Determines whether the bitmap fill mode is repeat. * * @param node The BitmapFillNode. * @return boolean if bitmap should repeat. */ public static boolean bitmapFillModeIsRepeat(BitmapFillNode node) { if (((node.getFileVersion().equalTo(FXGVersion.v1_0)) && node.repeat) || (node.fillMode.equals(FillMode.REPEAT))) { return true; } return false; } public static boolean isBitmapFillWithClip(IFillNode fill) { if (fill == null) return false; if (fill instanceof BitmapFillNode) { BitmapFillNode bFill = (BitmapFillNode) fill; if (ImageHelper.bitmapFillModeIsRepeat(bFill)) { return false; } else { if ((bFill.getFileVersion().equalTo(FXGVersion.v2_0)) && (bFill.fillMode == FillMode.SCALE)) { if (Double.isNaN(bFill.scaleX) && Double.isNaN(bFill.scaleY) && Double.isNaN(bFill.x) && Double.isNaN(bFill.y) && (Double.isNaN(bFill.rotation) || Math.abs(bFill.rotation) < AbstractFXGNode.EPSILON) && bFill.matrix == null) return false; else return true; } else { return true; } } } return false; } public static DefineImage createDefineBits(InputStream in, String mimeType) throws IOException { // TODO: Investigate faster mechanisms of getting image info and pixels byte[] bytes = FileUtils.toByteArray(in); Image image = getImage(bytes); if (mimeType == null) { throw new IOException("Unsupported MIME type"); } PixelGrabber pixelGrabber = null; try { pixelGrabber = getPixelGrabber(image, null); } catch (Exception e) { throw new IOException("Error reading image"); } int width = pixelGrabber.getWidth(); int height = pixelGrabber.getHeight(); // JPEG if (MIME_JPG.equals(mimeType) || MIME_JPEG.equals(mimeType)) { DefineBitsJPEG2Tag imageTag = new DefineBitsJPEG2Tag(); imageTag.setImageData(bytes); return new DefineImage(imageTag, width, height); } // PNG or GIF else if (MIME_PNG.equals(mimeType) || MIME_GIF.equals(mimeType)) { int[] pixels = (int[])pixelGrabber.getPixels(); DefineImage defimage = createDefineBitsLossless(pixels, width, height); return defimage; } else { throw new IOException("Unsupported MIME type: " + mimeType); } } public static DefineImage createDefineBitsLossless(int[] pixels, int width, int height) throws IOException { DefineBitsLossless2Tag defineBitsLossless = new DefineBitsLossless2Tag(); defineBitsLossless.setBitmapFormat(DefineBitsLosslessTag.BF_24BIT_RGB_IMAGE); defineBitsLossless.setBitmapWidth(width); defineBitsLossless.setBitmapHeight(height); byte data[] = new byte[pixels.length * 4]; for (int i = 0; i < pixels.length; i++) { int offset = i * 4; int alpha = (pixels[i] >> 24) & 0xFF; data[offset] = (byte)alpha; // Premultiply the alpha channel if (data[offset] != 0) { int red = (pixels[i] >> 16) & 0xFF; data[offset + 1] = (byte)((red * alpha) / 255); int green = (pixels[i] >> 8) & 0xFF; data[offset + 2] = (byte)((green * alpha) / 255); int blue = pixels[i] & 0xFF; data[offset + 3] = (byte)((blue * alpha) / 255); } } defineBitsLossless.setZlibBitmapData(JPEGTranscoder.deflate(data)); return new DefineImage(defineBitsLossless, width, height); } public static DefineShapeTag create9SlicedShape(DefineImage bitmap, Rect r, double width, double height) { // Use default width/height information if none specified if (Double.isNaN(width)) width = bitmap.getWidth(); if (Double.isNaN(height)) height = bitmap.getHeight(); int slt = r.xMin(); int srt = r.xMax(); int stt = r.yMin(); int sbt = r.yMax(); FillStyleArray fillStyleArray = new FillStyleArray(9); LineStyleArray lineStyleArray = new LineStyleArray(); List<ShapeRecord> shapeRecords = new ArrayList<ShapeRecord>(50); // Apply runtime scale of 20x for twips Matrix matrix = new Matrix(); double twx = (ISWFConstants.TWIPS_PER_PIXEL); matrix.setScale(twx, twx); // Create 9 identical fillstyles as a work around for (int i = 0; i < 9; i++) { FillStyle fs = new FillStyle(FillStyle.NON_SMOOTHED_REPEATING_BITMAP, matrix, bitmap.getTag()); fillStyleArray.add(fs); } Styles styles = new Styles(fillStyleArray, lineStyleArray); int dxa = slt; int dxb = srt - slt; int dxc = (int)(bitmap.getWidth() * ISWFConstants.TWIPS_PER_PIXEL) - srt; int dya = stt; int dyb = sbt - stt; int dyc = (int)(bitmap.getHeight() * ISWFConstants.TWIPS_PER_PIXEL) - sbt; // border StyleChangeRecord scr = new StyleChangeRecord(); scr.setMove(0, dya); scr.setDefinedStyles(-1, 1, -1, styles); shapeRecords.add(scr); shapeRecords.add(new StraightEdgeRecord(0, -dya)); shapeRecords.add(new StraightEdgeRecord(dxa, 0)); scr = new StyleChangeRecord(); scr.setDefinedStyles(-1, 2, -1, styles); shapeRecords.add(scr); shapeRecords.add(new StraightEdgeRecord(dxb, 0)); scr = new StyleChangeRecord(); scr.setDefinedStyles(-1, 3, -1, styles); shapeRecords.add(scr); shapeRecords.add(new StraightEdgeRecord(dxc, 0)); shapeRecords.add(new StraightEdgeRecord(0, dya)); scr = new StyleChangeRecord(); scr.setDefinedStyles(-1, 6, -1, styles); shapeRecords.add(scr); shapeRecords.add(new StraightEdgeRecord(0, dyb)); scr = new StyleChangeRecord(); scr.setDefinedStyles(-1, 9, -1, styles); shapeRecords.add(scr); shapeRecords.add(new StraightEdgeRecord(0, dyc)); shapeRecords.add(new StraightEdgeRecord(-dxc, 0)); scr = new StyleChangeRecord(); scr.setDefinedStyles(-1, 8, -1, styles); shapeRecords.add(scr); shapeRecords.add(new StraightEdgeRecord(-dxb, 0)); scr = new StyleChangeRecord(); scr.setDefinedStyles(-1, 7, -1, styles); shapeRecords.add(scr); shapeRecords.add(new StraightEdgeRecord(-dxa, 0)); shapeRecords.add(new StraightEdgeRecord(0, -dyc)); scr = new StyleChangeRecord(); scr.setDefinedStyles(-1, 4, -1, styles); shapeRecords.add(scr); shapeRecords.add(new StraightEdgeRecord(0, -dyb)); // down 1 scr = new StyleChangeRecord(); scr.setMove(dxa, 0); scr.setDefinedStyles(2, 1, -1, styles); shapeRecords.add(scr); shapeRecords.add(new StraightEdgeRecord(0, dya)); scr = new StyleChangeRecord(); scr.setDefinedStyles(5, 4, -1, styles); shapeRecords.add(scr); shapeRecords.add(new StraightEdgeRecord(0, dyb)); scr = new StyleChangeRecord(); scr.setDefinedStyles(8, 7, -1, styles); shapeRecords.add(scr); shapeRecords.add(new StraightEdgeRecord(0, dyc)); // down 2 scr = new StyleChangeRecord(); scr.setMove(dxa + dxb, 0); scr.setDefinedStyles(3, 2, -1, styles); shapeRecords.add(scr); shapeRecords.add(new StraightEdgeRecord(0, dya)); scr = new StyleChangeRecord(); scr.setDefinedStyles(6, 5, -1, styles); shapeRecords.add(scr); shapeRecords.add(new StraightEdgeRecord(0, dyb)); scr = new StyleChangeRecord(); scr.setDefinedStyles(9, 8, -1, styles); shapeRecords.add(scr); shapeRecords.add(new StraightEdgeRecord(0, dyc)); // right 1 scr = new StyleChangeRecord(); scr.setMove(0, dya); scr.setDefinedStyles(1, 4, -1, styles); shapeRecords.add(scr); shapeRecords.add(new StraightEdgeRecord(dxa, 0)); scr = new StyleChangeRecord(); scr.setDefinedStyles(2, 5, -1, styles); shapeRecords.add(scr); shapeRecords.add(new StraightEdgeRecord(dxb, 0)); scr = new StyleChangeRecord(); scr.setDefinedStyles(3, 6, -1, styles); shapeRecords.add(scr); shapeRecords.add(new StraightEdgeRecord(dxc, 0)); // right 2 scr = new StyleChangeRecord(); scr.setMove(0, dya + dyb); scr.setDefinedStyles(4, 7, -1, styles); shapeRecords.add(scr); shapeRecords.add(new StraightEdgeRecord(dxa, 0)); scr = new StyleChangeRecord(); scr.setDefinedStyles(5, 8, -1, styles); shapeRecords.add(scr); shapeRecords.add(new StraightEdgeRecord(dxb, 0)); scr = new StyleChangeRecord(); scr.setDefinedStyles(6, 9, -1, styles); shapeRecords.add(scr); shapeRecords.add(new StraightEdgeRecord(dxc, 0)); ShapeWithStyle sws = new ShapeWithStyle(styles); sws.addShapeRecords(shapeRecords); DefineShape4Tag shape = new DefineShape4Tag(); shape.setShapeBounds(TypeHelper.rect(width, height)); shape.setEdgeBounds(shape.getShapeBounds()); shape.setShapes(sws); return shape; } public static String guessMimeType(String path) { if (path != null) { path = path.toLowerCase(); if (path.endsWith(".png")) return MIME_PNG; if (path.endsWith(".gif")) return MIME_GIF; if (path.endsWith(".jpg")) return MIME_JPG; if (path.endsWith(".jpeg")) return MIME_JPEG; } return null; } private static Image getImage(byte[] bytes) { Image image; try { image = Toolkit.getDefaultToolkit().createImage(bytes); } catch (InternalError ie) { if (Trace.error) { ie.printStackTrace(); } throw new InternalError("An error occurred because there is no graphics environment available. Please set the headless-server setting in the Flex configuration file to true."); } catch (NoClassDefFoundError ce) { if (Trace.error) { ce.printStackTrace(); } throw new InternalError("An error occurred because there is no graphics environment available. Please set the headless-server setting in the Flex configuration file to true."); } return image; } private static PixelGrabber getPixelGrabber(Image image, String location) { PixelGrabber pixelGrabber = new PixelGrabber(image, 0, 0, -1, -1, true); try { pixelGrabber.grabPixels(); } catch (InterruptedException interruptedException) { if (Trace.error) { interruptedException.printStackTrace(); } throw new RuntimeException("Failed to grab pixels for image " + location); } if (((pixelGrabber.getStatus() & ImageObserver.WIDTH) == 0) || ((pixelGrabber.getStatus() & ImageObserver.HEIGHT) == 0)) { throw new RuntimeException("Failed to grab pixels for image " + location); } return pixelGrabber; } }