blob: 6410f5d800170a713f2c6bd33b95837e94c3550e [file] [log] [blame]
/*
*
* 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.royale.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.royale.compiler.fxg.FXGVersion;
import org.apache.royale.compiler.internal.embedding.transcoders.JPEGTranscoder;
import org.apache.royale.compiler.internal.fxg.dom.AbstractFXGNode;
import org.apache.royale.compiler.internal.fxg.dom.BitmapGraphicNode;
import org.apache.royale.compiler.internal.fxg.dom.IFillNode;
import org.apache.royale.compiler.internal.fxg.dom.fills.BitmapFillNode;
import org.apache.royale.compiler.internal.fxg.dom.types.FillMode;
import org.apache.royale.swf.ISWFConstants;
import org.apache.royale.swf.tags.DefineBitsJPEG2Tag;
import org.apache.royale.swf.tags.DefineBitsLossless2Tag;
import org.apache.royale.swf.tags.DefineBitsLosslessTag;
import org.apache.royale.swf.tags.DefineShape4Tag;
import org.apache.royale.swf.tags.DefineShapeTag;
import org.apache.royale.swf.types.FillStyle;
import org.apache.royale.swf.types.FillStyleArray;
import org.apache.royale.swf.types.LineStyleArray;
import org.apache.royale.swf.types.Matrix;
import org.apache.royale.swf.types.Rect;
import org.apache.royale.swf.types.ShapeRecord;
import org.apache.royale.swf.types.ShapeWithStyle;
import org.apache.royale.swf.types.StraightEdgeRecord;
import org.apache.royale.swf.types.StyleChangeRecord;
import org.apache.royale.swf.types.Styles;
import org.apache.royale.utils.FileUtils;
import org.apache.royale.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;
}
}