blob: 14361cc32cc15980848ec41a32efd03e3d1559f3 [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.embedding.transcoders;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.PixelGrabber;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.royale.compiler.common.ISourceLocation;
import org.apache.royale.compiler.embedding.EmbedAttribute;
import org.apache.royale.compiler.internal.embedding.EmbedData;
import org.apache.royale.compiler.internal.workspaces.Workspace;
import org.apache.royale.compiler.problems.EmbedExceptionWhileTranscodingProblem;
import org.apache.royale.compiler.problems.ICompilerProblem;
import org.apache.royale.swf.ISWFConstants;
import org.apache.royale.swf.tags.DefineBitsJPEG2Tag;
import org.apache.royale.swf.tags.DefineBitsTag;
import org.apache.royale.swf.tags.DefineScalingGridTag;
import org.apache.royale.swf.tags.DefineShape4Tag;
import org.apache.royale.swf.tags.DefineShapeTag;
import org.apache.royale.swf.tags.DefineSpriteTag;
import org.apache.royale.swf.tags.ICharacterTag;
import org.apache.royale.swf.tags.ITag;
import org.apache.royale.swf.tags.PlaceObject2Tag;
import org.apache.royale.swf.types.FillStyle;
import org.apache.royale.swf.types.FillStyleArray;
import org.apache.royale.swf.types.IFillStyle;
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.ShapeWithStyle;
import org.apache.royale.swf.types.StraightEdgeRecord;
import org.apache.royale.swf.types.StyleChangeRecord;
import org.apache.royale.swf.types.Styles;
/**
* Handle the embedding of images which don't need to be transcoded
*/
public class ImageTranscoder extends ScalableTranscoder
{
protected static class ImageInfo
{
public ImageInfo(PixelGrabber pixelGrabber)
{
this.pixelGrabber = pixelGrabber;
this.width = pixelGrabber.getWidth();
this.height = pixelGrabber.getHeight();
this.widthInTwips = this.width * ISWFConstants.TWIPS_PER_PIXEL;
this.heightInTwips = this.height * ISWFConstants.TWIPS_PER_PIXEL;
}
public final PixelGrabber pixelGrabber;
public final int width;
public final int height;
public final int widthInTwips;
public final int heightInTwips;
}
/**
* Constructor.
*
* @param data The embedding data.
* @param workspace The workspace.
*/
public ImageTranscoder(EmbedData data, Workspace workspace)
{
super(data, workspace);
this.smoothing = false;
}
private boolean smoothing;
@Override
public boolean analyze(ISourceLocation location, Collection<ICompilerProblem> problems)
{
boolean result = super.analyze(location, problems);
if (scaling || smoothing)
baseClassQName = CORE_PACKAGE + ".SpriteAsset";
else
baseClassQName = CORE_PACKAGE + ".BitmapAsset";
return result;
}
@Override
protected boolean setAttribute(EmbedAttribute attribute)
{
boolean isSupported = true;
switch (attribute)
{
case SMOOTHING:
smoothing = (Boolean)data.getAttribute(EmbedAttribute.SMOOTHING);
break;
default:
isSupported = super.setAttribute(attribute);
}
return isSupported;
}
@Override
protected Map<String, ICharacterTag> doTranscode(Collection<ITag> tags, Collection<ICompilerProblem> problems)
{
byte[] bytes = getDataBytes(problems);
if (bytes == null)
return null;
ImageInfo imageInfo = null;
if (scaling || smoothing)
{
try
{
imageInfo = getImageInfo(bytes, problems);
}
catch (Exception e)
{
problems.add(new EmbedExceptionWhileTranscodingProblem(e));
return null;
}
}
DefineBitsTag image = buildImage(bytes, problems);
if (image == null)
return null;
ICharacterTag assetTag;
if (scaling)
{
assetTag = buildSlicedSprite(image, imageInfo.widthInTwips, imageInfo.heightInTwips, tags, problems);
}
else if (smoothing)
{
assetTag = buildSmoothingSprite(image, imageInfo.widthInTwips, imageInfo.heightInTwips, tags, problems);
}
else
{
assetTag = image;
}
if (assetTag == null)
return null;
Map<String, ICharacterTag> symbolTags = Collections.singletonMap(data.getQName(), (ICharacterTag)assetTag);
return symbolTags;
}
protected ImageInfo getImageInfo(byte[] bytes, Collection<ICompilerProblem> problems) throws Exception
{
// TODO This gets the image and width of the image.
// Need to remove this and come up with a way to get the dimensions
// without reading the whole file/using AWT.
// http://bugs.adobe.com/jira/browse/CMP-542
Image image = Toolkit.getDefaultToolkit().createImage(bytes);
PixelGrabber pixelGrabber = new PixelGrabber(image, 0, 0, -1, -1, true);
pixelGrabber.grabPixels();
ImageInfo imageInfo = new ImageInfo(pixelGrabber);
return imageInfo;
}
private DefineSpriteTag buildSlicedSprite(DefineBitsTag image, int width, int height, Collection<ITag> tags, Collection<ICompilerProblem> problems)
{
DefineScalingGridTag scalingGrid = buildScalingGrid();
DefineShapeTag shape = buildSlicedShape(image, scalingGrid.getSplitter(), width, height);
return buildSprite(image, shape, scalingGrid, tags);
}
private DefineSpriteTag buildSmoothingSprite(DefineBitsTag image, int width, int height, Collection<ITag> tags, Collection<ICompilerProblem> problems)
{
DefineShapeTag shape = buildSmoothingShape(image, width, height);
return buildSprite(image, shape, null, tags);
}
private DefineBitsTag buildImage(byte[] bytes, Collection<ICompilerProblem> problems)
{
DefineBitsJPEG2Tag tag = new DefineBitsJPEG2Tag();
tag.setImageData(bytes);
return tag;
}
private DefineSpriteTag buildSprite(DefineBitsTag image, DefineShapeTag shape, DefineScalingGridTag scalingGrid, Collection<ITag> tags)
{
tags.add(image);
Matrix tm = new Matrix();
tm.setScale(1, 1);
PlaceObject2Tag po = new PlaceObject2Tag();
po.setDepth(10);
po.setCharacter(shape);
po.setHasCharacter(true);
po.setMatrix(tm);
po.setHasMatrix(true);
List<ITag> spriteTags = new ArrayList<ITag>(1);
spriteTags.add(po);
int frameCount = 0;
DefineSpriteTag sprite = buildSprite(spriteTags, frameCount, scalingGrid, tags);
tags.add(shape);
return sprite;
}
private DefineShapeTag buildSlicedShape(DefineBitsTag image, Rect r, int width, int height)
{
DefineShape4Tag shape = new DefineShape4Tag();
Rect bounds = new Rect(0, width, 0, height);
shape.setShapeBounds(bounds);
shape.setEdgeBounds(bounds);
FillStyleArray fillStyles = new FillStyleArray();
LineStyleArray lineStyles = new LineStyleArray();
Styles styles = new Styles(fillStyles, lineStyles);
ShapeWithStyle shapeWithStyle = new ShapeWithStyle(styles);
shape.setShapes(shapeWithStyle);
// translate into source bitmap
Matrix tsm = new Matrix();
// unity in twips
tsm.setScale(ISWFConstants.TWIPS_PER_PIXEL, ISWFConstants.TWIPS_PER_PIXEL);
// 9 identical fillstyles to fool things
// not sure why this is needed, and why we're indexing into
// the into, as the values are identical. Was ported over from hero.
for (int i = 0; i < 9; ++i)
{
FillStyle fs = new FillStyle();
fs.setBitmapCharacter(image);
fs.setBitmapMatrix(tsm);
fs.setFillStyleType(FillStyle.NON_SMOOTHED_REPEATING_BITMAP);
fillStyles.add(fs);
}
int slt = r.xMin();
int srt = r.xMax();
int stt = r.yMin();
int sbt = r.yMax();
int dxa = slt;
int dxb = srt - slt;
int dxc = width - srt;
int dya = stt;
int dyb = sbt - stt;
int dyc = height - sbt;
StyleChangeRecord startStyle = new StyleChangeRecord();
startStyle.setMove(0, dya);
shapeWithStyle.addShapeRecord(startStyle);
// border
addEdgesWithFill(styles, shapeWithStyle, new int[][] { {0, -dya}, {dxa, 0}}, 0, 1);
addEdgesWithFill(styles, shapeWithStyle, new int[][] {{dxb, 0}}, 0, 2);
addEdgesWithFill(styles, shapeWithStyle, new int[][] { {dxc, 0}, {0, dya}}, 0, 3);
addEdgesWithFill(styles, shapeWithStyle, new int[][] {{0, dyb}}, 0, 6);
addEdgesWithFill(styles, shapeWithStyle, new int[][] { {0, dyc}, {-dxc, 0}}, 0, 9);
addEdgesWithFill(styles, shapeWithStyle, new int[][] {{-dxb, 0}}, 0, 8);
addEdgesWithFill(styles, shapeWithStyle, new int[][] { {-dxa, 0}, {0, -dyc}}, 0, 7);
addEdgesWithFill(styles, shapeWithStyle, new int[][] {{0, -dyb}}, 0, 4);
// down 1
StyleChangeRecord down1Style = new StyleChangeRecord();
down1Style.setMove(dxa, 0);
shapeWithStyle.addShapeRecord(down1Style);
addEdgesWithFill(styles, shapeWithStyle, new int[][] {{0, dya}}, 2, 1);
addEdgesWithFill(styles, shapeWithStyle, new int[][] {{0, dyb}}, 5, 4);
addEdgesWithFill(styles, shapeWithStyle, new int[][] {{0, dyc}}, 8, 7);
// down 2
StyleChangeRecord down2Style = new StyleChangeRecord();
down2Style.setMove(dxa + dxb, 0);
shapeWithStyle.addShapeRecord(down2Style);
addEdgesWithFill(styles, shapeWithStyle, new int[][] {{0, dya}}, 3, 2);
addEdgesWithFill(styles, shapeWithStyle, new int[][] {{0, dyb}}, 6, 5);
addEdgesWithFill(styles, shapeWithStyle, new int[][] {{0, dyc}}, 9, 8);
// right 1
StyleChangeRecord right1Style = new StyleChangeRecord();
right1Style.setMove(0, dya);
shapeWithStyle.addShapeRecord(right1Style);
addEdgesWithFill(styles, shapeWithStyle, new int[][] {{dxa, 0}}, 1, 4);
addEdgesWithFill(styles, shapeWithStyle, new int[][] {{dxb, 0}}, 2, 5);
addEdgesWithFill(styles, shapeWithStyle, new int[][] {{dxc, 0}}, 3, 6);
// right 2
StyleChangeRecord right2Style = new StyleChangeRecord();
right2Style.setMove(0, dya + dyb);
shapeWithStyle.addShapeRecord(right2Style);
addEdgesWithFill(styles, shapeWithStyle, new int[][] {{dxa, 0}}, 4, 7);
addEdgesWithFill(styles, shapeWithStyle, new int[][] {{dxb, 0}}, 5, 8);
addEdgesWithFill(styles, shapeWithStyle, new int[][] {{dxc, 0}}, 6, 9);
return shape;
}
private DefineShapeTag buildSmoothingShape(DefineBitsTag image, int width, int height)
{
DefineShapeTag shape = new DefineShapeTag();
Rect bounds = new Rect(0, width, 0, height);
shape.setShapeBounds(bounds);
final FillStyleArray fillStyles = new FillStyleArray();
final LineStyleArray lineStyles = new LineStyleArray();
Styles styles = new Styles(fillStyles, lineStyles);
ShapeWithStyle shapeWithStyle = new ShapeWithStyle(styles);
shape.setShapes(shapeWithStyle);
// translate into source bitmap
Matrix tsm = new Matrix();
// unity in twips
tsm.setScale(ISWFConstants.TWIPS_PER_PIXEL, ISWFConstants.TWIPS_PER_PIXEL);
FillStyle fs = new FillStyle();
fs.setBitmapCharacter(image);
fs.setBitmapMatrix(tsm);
fs.setFillStyleType(FillStyle.CLIPPED_BITMAP_FILL);
fillStyles.add(fs);
StyleChangeRecord startStyle = new StyleChangeRecord();
// We use fillstyle1, because it matches what FlashAuthoring generates.
startStyle.setDefinedStyles(null, fs, null, styles);
startStyle.setMove(width, height);
shapeWithStyle.addShapeRecord(startStyle);
// border
shapeWithStyle.addShapeRecord(new StraightEdgeRecord(-1 * width, 0));
shapeWithStyle.addShapeRecord(new StraightEdgeRecord(0, -1 * height));
shapeWithStyle.addShapeRecord(new StraightEdgeRecord(width, 0));
shapeWithStyle.addShapeRecord(new StraightEdgeRecord(0, height));
return shape;
}
private void addEdgesWithFill(Styles styles, ShapeWithStyle shapeWithStyle, int[][] coords, int left, int right)
{
StyleChangeRecord scr = new StyleChangeRecord();
if ((left != 0) || (right != 0))
{
IFillStyle fillStyle0 = null;
if (left > 0)
{
fillStyle0 = styles.getFillStyles().get(left - 1);
}
IFillStyle fillStyle1 = null;
if (right > 0)
{
fillStyle1 = styles.getFillStyles().get(right - 1);
}
scr.setDefinedStyles(fillStyle0, fillStyle1, null, styles);
}
shapeWithStyle.addShapeRecord(scr);
for (int i = 0; i < coords.length; ++i)
{
shapeWithStyle.addShapeRecord(new StraightEdgeRecord(coords[i][0], coords[i][1]));
}
}
@Override
public boolean equals(Object o)
{
if (!super.equals(o))
return false;
if (!(o instanceof ImageTranscoder))
return false;
ImageTranscoder t = (ImageTranscoder)o;
if (smoothing != t.smoothing)
return false;
return true;
}
@Override
public int hashCode()
{
int hashCode = super.hashCode();
hashCode += (smoothing ? 1 : 0);
return hashCode;
}
}