#1156 9-slice scaling - better stroke scaling

This commit is contained in:
Jindra Petřík
2016-02-24 21:14:14 +01:00
parent 0beca77f48
commit 8e62079f55
22 changed files with 495 additions and 320 deletions

View File

@@ -122,7 +122,7 @@ public class ShapeExporter {
Matrix m = new Matrix();
m.translate(-rect.Xmin, -rect.Ymin);
m.scale(settings.zoom);
st.toImage(0, 0, 0, new RenderContext(), img, false, m, new Matrix(), m, new CXFORMWITHALPHA());
st.toImage(0, 0, 0, new RenderContext(), img, false, m, m, m, new CXFORMWITHALPHA());
if (settings.mode == ShapeExportMode.PNG) {
ImageHelper.write(img.getBufferedImage(), ImageFormat.PNG, file);
} else {

View File

@@ -17,7 +17,6 @@
package com.jpexs.decompiler.flash.exporters.commonshape;
import com.jpexs.decompiler.flash.types.RECT;
import java.awt.Point;
import java.awt.geom.Rectangle2D;
/**
@@ -64,8 +63,14 @@ public class ExportRectangle {
}
public boolean contains(Point point) {
int x = point.x;
int y = point.y;
double x = point.x;
double y = point.y;
return xMin <= x && xMax >= x && yMin <= y && yMax >= y;
}
public boolean contains(java.awt.Point point) {
double x = point.x;
double y = point.y;
return xMin <= x && xMax >= x && yMin <= y && yMax >= y;
}

View File

@@ -83,6 +83,8 @@ public class BitmapExporter extends ShapeExporterBase {
private Stroke defaultStroke;
private Matrix strokeTransformation;
private class TransformedStroke implements Stroke {
/**
@@ -134,9 +136,9 @@ public class BitmapExporter extends ShapeExporterBase {
}
}
public static void export(SWF swf, SHAPE shape, Color defaultColor, SerializableImage image, Matrix transformation, ColorTransform colorTransform) {
public static void export(SWF swf, SHAPE shape, Color defaultColor, SerializableImage image, Matrix transformation, Matrix strokeTransformation, ColorTransform colorTransform) {
BitmapExporter exporter = new BitmapExporter(swf, shape, defaultColor, colorTransform);
exporter.exportTo(image, transformation);
exporter.exportTo(image, transformation, strokeTransformation);
}
private BitmapExporter(SWF swf, SHAPE shape, Color defaultColor, ColorTransform colorTransform) {
@@ -145,8 +147,12 @@ public class BitmapExporter extends ShapeExporterBase {
this.defaultColor = defaultColor;
}
private void exportTo(SerializableImage image, Matrix transformation) {
private void exportTo(SerializableImage image, Matrix transformation, Matrix strokeTransformation) {
this.image = image;
this.strokeTransformation = strokeTransformation.clone();
this.strokeTransformation.scaleX /= SWF.unitDivisor;
this.strokeTransformation.scaleY /= SWF.unitDivisor;
graphics = (Graphics2D) image.getGraphics();
AffineTransform at = transformation.toTransform();
at.preConcatenate(AffineTransform.getScaleInstance(1 / SWF.unitDivisor, 1 / SWF.unitDivisor));
@@ -354,13 +360,13 @@ public class BitmapExporter extends ShapeExporterBase {
}
switch (scaleMode) {
case "VERTICAL":
thickness *= graphics.getTransform().getScaleY();
thickness *= strokeTransformation.scaleY;
break;
case "HORIZONTAL":
thickness *= graphics.getTransform().getScaleX();
thickness *= strokeTransformation.scaleX;
break;
case "NORMAL":
thickness *= Math.max(graphics.getTransform().getScaleX(), graphics.getTransform().getScaleY());
thickness *= Math.max(strokeTransformation.scaleX, strokeTransformation.scaleY);
break;
}
@@ -376,7 +382,9 @@ public class BitmapExporter extends ShapeExporterBase {
// Do not scale strokes automatically:
try {
lineStroke = new TransformedStroke(lineStroke, graphics.getTransform());
AffineTransform t = (AffineTransform) graphics.getTransform().clone();
t.translate(-0.5, -0.5);
lineStroke = new TransformedStroke(lineStroke, t);
} catch (NoninvertibleTransformException net) {
// ignore
}

View File

@@ -22,6 +22,8 @@ import com.jpexs.decompiler.flash.types.ColorTransform;
import com.jpexs.decompiler.flash.types.GRADRECORD;
import com.jpexs.decompiler.flash.types.RGB;
import com.jpexs.decompiler.flash.types.SHAPE;
import java.awt.BasicStroke;
import java.awt.Shape;
import java.awt.geom.GeneralPath;
import java.util.ArrayList;
import java.util.List;
@@ -33,12 +35,19 @@ import java.util.List;
public class PathExporter extends ShapeExporterBase {
private final List<GeneralPath> paths = new ArrayList<>();
private final List<GeneralPath> strokes = new ArrayList<>();
private double thickness = 0;
private GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
public static List<GeneralPath> export(SWF swf, SHAPE shape) {
return export(swf, shape, new ArrayList<>());
}
public static List<GeneralPath> export(SWF swf, SHAPE shape, List<GeneralPath> strokes) {
PathExporter exporter = new PathExporter(swf, shape, null);
exporter.export();
strokes.addAll(exporter.strokes);
return exporter.paths;
}
@@ -104,6 +113,7 @@ public class PathExporter extends ShapeExporterBase {
@Override
public void lineStyle(double thickness, RGB color, boolean pixelHinting, String scaleMode, int startCaps, int endCaps, int joints, float miterLimit) {
finalizePath();
this.thickness = thickness;
}
@Override
@@ -127,6 +137,11 @@ public class PathExporter extends ShapeExporterBase {
}
protected void finalizePath() {
if (thickness == 0) {
strokes.add(new GeneralPath());
} else {
strokes.add(new GeneralPath(new BasicStroke((float) (thickness)).createStrokedShape(path)));
}
paths.add(path);
path = new GeneralPath(GeneralPath.WIND_EVEN_ODD); //For correct intersections display
}

View File

@@ -1040,7 +1040,7 @@ public class SvgImporter {
st = (DefineShape4Tag) (new SvgImporter().importSvg(st, svgDataS));
swf.addTag(st);
SerializableImage si = new SerializableImage(480, 360, BufferedImage.TYPE_4BYTE_ABGR);
BitmapExporter.export(swf, st.shapes, Color.yellow, si, new Matrix(), null);
BitmapExporter.export(swf, st.shapes, Color.yellow, si, new Matrix(), new Matrix(), null);
List<Tag> li = new ArrayList<>();
li.add(st);
ImageIO.write(si.getBufferedImage(), "PNG", new File(name + ".imported.png"));

View File

@@ -916,7 +916,7 @@ public class DefineEditTextTag extends TextTag {
}
@Override
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix prevTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) {
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix strokeTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) {
render(TextRenderMode.BITMAP, image, null, null, transformation, colorTransform, 1);
}

View File

@@ -19,12 +19,24 @@ package com.jpexs.decompiler.flash.tags;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SWFInputStream;
import com.jpexs.decompiler.flash.SWFOutputStream;
import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle;
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
import com.jpexs.decompiler.flash.exporters.commonshape.Point;
import com.jpexs.decompiler.flash.tags.base.CharacterIdTag;
import com.jpexs.decompiler.flash.tags.base.CharacterTag;
import com.jpexs.decompiler.flash.tags.base.DrawableTag;
import com.jpexs.decompiler.flash.tags.base.RenderContext;
import com.jpexs.decompiler.flash.types.BasicType;
import com.jpexs.decompiler.flash.types.RECT;
import com.jpexs.decompiler.flash.types.annotations.SWFType;
import com.jpexs.decompiler.flash.types.annotations.SWFVersion;
import com.jpexs.helpers.ByteArrayRange;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.io.IOException;
/**
@@ -85,4 +97,134 @@ public class DefineScalingGridTag extends Tag implements CharacterIdTag {
public void setCharacterId(int characterId) {
this.characterId = characterId;
}
private static double roundPixels(double v) {
return v; //Math.rint(v / SWF.unitDivisor) * SWF.unitDivisor;
}
private static double roundPixels20(double v) {
return Math.rint(v / SWF.unitDivisor) * SWF.unitDivisor;
}
private static Matrix rectToRectMatrix(ExportRectangle fromRect, ExportRectangle toRect) {
Matrix toOrigin = Matrix.getTranslateInstance(roundPixels(-fromRect.xMin), roundPixels(-fromRect.yMin));
Matrix scale = new Matrix();
scale.scaleX = roundPixels(toRect.getWidth()) / roundPixels(fromRect.getWidth());
scale.scaleY = roundPixels(toRect.getHeight()) / roundPixels(fromRect.getHeight());
Matrix toDest = Matrix.getTranslateInstance(roundPixels(toRect.xMin), roundPixels(toRect.yMin));
return toOrigin.preConcatenate(scale).preConcatenate(toDest);
}
public RECT getRect() {
Shape s = getOutline(0, 0, 0, new RenderContext(), new Matrix(), new Matrix(), true);
if (s == null) {
return null;
}
Rectangle r = s.getBounds();
return new RECT(r.x, r.x + r.width, r.y, r.y + r.height);
}
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation, Matrix prevTransform, boolean stroked) {
CharacterTag ct = swf.getCharacter(characterId);
if (ct == null) {
return null;
}
if (!(ct instanceof DrawableTag)) {
return null;
}
double[] coords = new double[6];
DrawableTag dt = (DrawableTag) ct;
Shape path = dt.getOutline(frame, time, ratio, renderContext, transformation, stroked);
PathIterator iterator = path.getPathIterator(new AffineTransform());
GeneralPath gp = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
ExportRectangle boundsRect = new ExportRectangle(dt.getRect());
ExportRectangle scalingGrid = new ExportRectangle(splitter);
ExportRectangle[] sourceRect = new ExportRectangle[9];
ExportRectangle[] targetRect = new ExportRectangle[9];
Matrix[] transforms = new Matrix[9];
getSlices(transformation.transform(boundsRect), boundsRect, scalingGrid, sourceRect, targetRect, transforms);
while (!iterator.isDone()) {
int type = iterator.currentSegment(coords);
for (int i = 0; i < 6; i += 2) {
double x = coords[i];
double y = coords[i + 1];
for (int s = 0; s < 9; s++) {
Point p = new Point(x, y);
if (sourceRect[s].contains(p)) {
p = transforms[s].transform(p);
coords[i] = p.x;
coords[i + 1] = p.y;
break;
}
}
}
switch (type) {
case PathIterator.SEG_MOVETO:
gp.moveTo(coords[0], coords[1]);
break;
case PathIterator.SEG_LINETO:
gp.lineTo(coords[0], coords[1]);
break;
case PathIterator.SEG_QUADTO:
gp.quadTo(coords[0], coords[1], coords[2], coords[3]);
break;
case PathIterator.SEG_CUBICTO:
gp.curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]);
break;
case PathIterator.SEG_CLOSE:
gp.closePath();
break;
}
iterator.next();
}
return gp;
}
public static void getSlices(ExportRectangle targetBounds, ExportRectangle boundsRect, ExportRectangle scalingGrid, ExportRectangle[] sourceRect, ExportRectangle[] targetRect, Matrix[] transforms) {
double src_x[] = new double[]{boundsRect.xMin, scalingGrid.xMin, scalingGrid.xMax, boundsRect.xMax};
double dst_x[] = new double[]{targetBounds.xMin, targetBounds.xMin + scalingGrid.xMin, targetBounds.xMax - (boundsRect.xMax - scalingGrid.xMax), targetBounds.xMax};
double src_y[] = new double[]{boundsRect.yMin, scalingGrid.yMin, scalingGrid.yMax, boundsRect.yMax};
double dst_y[] = new double[]{targetBounds.yMin, targetBounds.yMin + scalingGrid.yMin, targetBounds.yMax - (boundsRect.yMax - scalingGrid.yMax), targetBounds.yMax};
int pos = 0;
for (int sy = 0; sy < 3; sy++) {
for (int sx = 0; sx < 3; sx++) {
sourceRect[pos] = new ExportRectangle(src_x[sx], src_y[sy], src_x[sx + 1], src_y[sy + 1]);
targetRect[pos] = new ExportRectangle(dst_x[sx], dst_y[sy], dst_x[sx + 1], dst_y[sy + 1]);
pos++;
}
}
for (int i = 0; i < targetRect.length; i++) {
/* sourceRect[i].xMax = roundPixels20(sourceRect[i].xMax);
sourceRect[i].yMax = roundPixels20(sourceRect[i].yMax);
sourceRect[i].xMin = roundPixels20(sourceRect[i].xMin);
sourceRect[i].yMin = roundPixels20(sourceRect[i].yMin);
*/
//System.out.println("source[" + i + "]=" + sourceRect[i]);
//System.out.println("target[" + i + "]=" + targetRect[i]);
/*targetRect[i].xMax = roundPixels20(targetRect[i].xMax);
targetRect[i].yMax = roundPixels20(targetRect[i].yMax);
targetRect[i].xMin = roundPixels20(targetRect[i].xMin);
targetRect[i].yMin = roundPixels20(targetRect[i].yMin);
*/
transforms[i] = rectToRectMatrix(sourceRect[i], targetRect[i]);
targetRect[i].xMax = Math.rint(targetRect[i].xMax / SWF.unitDivisor);
targetRect[i].yMax = Math.rint(targetRect[i].yMax / SWF.unitDivisor);
targetRect[i].xMin = Math.rint(targetRect[i].xMin / SWF.unitDivisor);
targetRect[i].yMin = Math.rint(targetRect[i].yMin / SWF.unitDivisor);
//targetRect[i].xMax += maxStroke;
//Round to pixel boundary
}
}
}

View File

@@ -361,13 +361,13 @@ public class DefineSpriteTag extends CharacterTag implements DrawableTag, Timeli
}
@Override
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation) {
return getTimeline().getOutline(frame, time, renderContext, transformation);
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation, boolean stroked) {
return getTimeline().getOutline(frame, time, renderContext, transformation, stroked);
}
@Override
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix prevTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) {
getTimeline().toImage(frame, time, renderContext, image, isClip, transformation, prevTransformation, absoluteTransformation, colorTransform);
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix strokeTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) {
getTimeline().toImage(frame, time, renderContext, image, isClip, transformation, strokeTransformation, absoluteTransformation, colorTransform);
}
@Override

View File

@@ -87,13 +87,13 @@ public abstract class ButtonTag extends CharacterTag implements DrawableTag, Tim
}
@Override
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation) {
return getTimeline().getOutline(frame, time, renderContext, transformation);
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation, boolean stroked) {
return getTimeline().getOutline(frame, time, renderContext, transformation, stroked);
}
@Override
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix prevTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) {
getTimeline().toImage(frame, time, renderContext, image, isClip, transformation, prevTransformation, absoluteTransformation, colorTransform);
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix strokeTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) {
getTimeline().toImage(frame, time, renderContext, image, isClip, transformation, strokeTransformation, absoluteTransformation, colorTransform);
}
@Override

View File

@@ -17,8 +17,11 @@
package com.jpexs.decompiler.flash.tags.base;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle;
import com.jpexs.decompiler.flash.tags.DefineScalingGridTag;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.helpers.ByteArrayRange;
import java.util.List;
/**
*
@@ -74,4 +77,14 @@ public abstract class CharacterTag extends Tag implements CharacterIdTag {
public String getExportName() {
return exportName;
}
public DefineScalingGridTag getScalingGridTag() {
List<CharacterIdTag> mtags = swf.getCharacterIdTags(getCharacterId());
for (CharacterIdTag ct : mtags) {
if (ct instanceof DefineScalingGridTag) {
return (DefineScalingGridTag) ct;
}
}
return null;
}
}

View File

@@ -37,7 +37,7 @@ public interface DrawableTag extends BoundedTag {
public int getUsedParameters();
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation);
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation, boolean stroked);
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix prevTransformation, Matrix absoluteTransformation, ColorTransform colorTransform);

View File

@@ -317,13 +317,13 @@ public abstract class FontTag extends CharacterTag implements AloneTag, Drawable
}
@Override
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation) {
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation, boolean stroked) {
RECT r = getRect();
return new Area(new Rectangle(r.Xmin, r.Ymin, r.getWidth(), r.getHeight()));
}
@Override
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix prevTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) {
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix strokeTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) {
SHAPERECORD.shapeListToImage(swf, getGlyphShapeTable(), image, frame, Color.black, colorTransform);
}

View File

@@ -278,13 +278,13 @@ public abstract class ImageTag extends CharacterTag implements DrawableTag {
}
@Override
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation) {
return transformation.toTransform().createTransformedShape(getShape().getOutline(swf));
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation, boolean stroked) {
return transformation.toTransform().createTransformedShape(getShape().getOutline(swf, stroked));
}
@Override
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix prevTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) {
BitmapExporter.export(swf, getShape(), null, image, transformation, colorTransform);
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix strokeTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) {
BitmapExporter.export(swf, getShape(), null, image, transformation, strokeTransformation, colorTransform);
}
@Override

View File

@@ -279,13 +279,13 @@ public abstract class MorphShapeTag extends CharacterTag implements DrawableTag
}
@Override
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix prevTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) {
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix strokeTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) {
SHAPEWITHSTYLE shape = getShapeAtRatio(ratio);
// morphShape using shapeNum=3, morphShape2 using shapeNum=4
// todo: Currently the generated image is not cached, because the cache
// key contains the hashCode of the finalRecord object, and it is always
// recreated
BitmapExporter.export(swf, shape, null, image, transformation, colorTransform);
BitmapExporter.export(swf, shape, null, image, transformation, strokeTransformation, colorTransform);
}
@Override
@@ -314,8 +314,8 @@ public abstract class MorphShapeTag extends CharacterTag implements DrawableTag
}
@Override
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation) {
return transformation.toTransform().createTransformedShape(getShapeAtRatio(ratio).getOutline(swf));
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation, boolean stroked) {
return transformation.toTransform().createTransformedShape(getShapeAtRatio(ratio).getOutline(swf, stroked));
}
@Override

View File

@@ -127,13 +127,13 @@ public abstract class ShapeTag extends CharacterTag implements DrawableTag, Lazy
}
@Override
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation) {
return transformation.toTransform().createTransformedShape(getShapes().getOutline(swf));
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation, boolean stroked) {
return transformation.toTransform().createTransformedShape(getShapes().getOutline(swf, stroked));
}
@Override
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix prevTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) {
BitmapExporter.export(swf, getShapes(), null, image, transformation, colorTransform);
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix strokeTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) {
BitmapExporter.export(swf, getShapes(), null, image, transformation, strokeTransformation, colorTransform);
if (Configuration._debugMode.get()) { // show control points
List<GeneralPath> paths = PathExporter.export(swf, getShapes());
double[] coords = new double[6];

View File

@@ -652,7 +652,7 @@ public abstract class StaticTextTag extends TextTag {
}
@Override
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix prevTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) {
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix strokeTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) {
staticTextToImage(swf, textRecords, getTextNum(), image, textMatrix, transformation, colorTransform);
/*try {
TextTag originalTag = (TextTag) getOriginalTag();

View File

@@ -392,7 +392,7 @@ public abstract class TextTag extends CharacterTag implements DrawableTag {
Graphics2D g = (Graphics2D) image.getGraphics();
Matrix mat = transformation.clone();
mat = mat.concatenate(new Matrix(textMatrix));
BitmapExporter.export(swf, getBorderShape(borderColor, fillColor, rect), null, image, mat, colorTransform);
BitmapExporter.export(swf, getBorderShape(borderColor, fillColor, rect), null, image, mat, mat, colorTransform);
}
public static void drawBorderHtmlCanvas(SWF swf, StringBuilder result, RGB borderColor, RGB fillColor, RECT rect, MATRIX textMatrix, ColorTransform colorTransform, double unitDivisor) {
@@ -470,7 +470,7 @@ public abstract class TextTag extends CharacterTag implements DrawableTag {
}
if (shape != null) {
BitmapExporter.export(swf, shape, textColor2, image, mat, colorTransform);
BitmapExporter.export(swf, shape, textColor2, image, mat, mat, colorTransform);
if (SHAPERECORD.DRAW_BOUNDING_BOX) {
RGB borderColor = new RGBA(Color.black);
RGB fillColor = new RGBA(new Color(255, 255, 255, 0));
@@ -736,7 +736,7 @@ public abstract class TextTag extends CharacterTag implements DrawableTag {
}
@Override
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation) {
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation, boolean stroked) {
RECT r = getBounds();
Shape shp = new Rectangle.Double(r.Xmin, r.Ymin, r.getWidth(), r.getHeight());
return transformation.toTransform().createTransformedShape(shp); //TODO: match character shapes (?)

View File

@@ -64,9 +64,9 @@ import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
@@ -74,6 +74,9 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
/**
*
@@ -549,27 +552,16 @@ public class Timeline {
return modified;
}
private Matrix rectToRectMatrix(ExportRectangle fromRect, ExportRectangle toRect) {
Matrix toOrigin = Matrix.getTranslateInstance(-fromRect.xMin, -fromRect.yMin);
Matrix scale = new Matrix();
scale.scaleX = toRect.getWidth() / fromRect.getWidth();
scale.scaleY = toRect.getHeight() / fromRect.getHeight();
Matrix toDest = Matrix.getTranslateInstance(toRect.xMin, toRect.yMin);
return toOrigin.preConcatenate(scale).preConcatenate(toDest);
}
public double roundToPixel(double val) {
return Math.rint(val / SWF.unitDivisor) * SWF.unitDivisor;
}
public void toImage(int frame, int time, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix prevTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) {
/*public void toImage(int frame, int time, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix prevTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) {
ExportRectangle scalingGrid = null;
if (timelined instanceof CharacterTag) {
List<CharacterIdTag> mtags = swf.getCharacterIdTags(((CharacterTag) timelined).getCharacterId());
for (CharacterIdTag ct : mtags) {
if (ct instanceof DefineScalingGridTag) {
scalingGrid = new ExportRectangle(((DefineScalingGridTag) ct).splitter);
}
DefineScalingGridTag sgt = ((CharacterTag) timelined).getScalingGridTag();
if (sgt != null) {
scalingGrid = new ExportRectangle(sgt.splitter);
}
}
@@ -588,71 +580,221 @@ public class Timeline {
ExportRectangle boundsRect = new ExportRectangle(timelined.getRect());
/*
0 | 1 | 2
------------
3 | 4 | 5
------------
6 | 7 | 8
*/
ExportRectangle[] targetRect = new ExportRectangle[9];
ExportRectangle[] sourceRect = new ExportRectangle[9];
Matrix[] transforms = new Matrix[9];
sourceRect[0] = new ExportRectangle(0, 0, scalingGrid.xMin, scalingGrid.yMin);
targetRect[0] = new ExportRectangle(0, 0, scalingGrid.xMin, scalingGrid.yMin);
ExportRectangle targetRect[] = new ExportRectangle[9];
ExportRectangle sourceRect[] = new ExportRectangle[9];
Matrix transforms[] = new Matrix[9];
sourceRect[1] = new ExportRectangle(scalingGrid.xMin, 0, scalingGrid.xMax, scalingGrid.yMin);
targetRect[1] = new ExportRectangle(scalingGrid.xMin, 0, boundsRect.xMax * transformation.scaleX - (boundsRect.xMax - scalingGrid.xMax), scalingGrid.yMin);
DefineScalingGridTag.getSlices(transformation, prevScale, boundsRect, scalingGrid, sourceRect, targetRect, transforms);
sourceRect[2] = new ExportRectangle(scalingGrid.xMax, 0, boundsRect.xMax, scalingGrid.yMin);
targetRect[2] = new ExportRectangle(
boundsRect.xMax * transformation.scaleX - (boundsRect.xMax - scalingGrid.xMax),
0,
boundsRect.xMax * transformation.scaleX, scalingGrid.yMin);
sourceRect[3] = new ExportRectangle(0, scalingGrid.yMin, scalingGrid.xMin, scalingGrid.yMax);
targetRect[3] = new ExportRectangle(0, scalingGrid.yMin, scalingGrid.xMin, boundsRect.yMax * transformation.scaleY - (boundsRect.yMax - scalingGrid.yMax));
sourceRect[4] = new ExportRectangle(scalingGrid.xMin, scalingGrid.yMin, scalingGrid.xMax, scalingGrid.yMax);
targetRect[4] = new ExportRectangle(scalingGrid.xMin, scalingGrid.yMin, boundsRect.xMax * transformation.scaleX - (boundsRect.xMax - scalingGrid.xMax), boundsRect.yMax * transformation.scaleY - (boundsRect.yMax - scalingGrid.yMax));
sourceRect[5] = new ExportRectangle(scalingGrid.xMax, scalingGrid.yMin, boundsRect.xMax, scalingGrid.yMax);
targetRect[5] = new ExportRectangle(
boundsRect.xMax * transformation.scaleX - (boundsRect.xMax - scalingGrid.xMax),
scalingGrid.yMin,
boundsRect.xMax * transformation.scaleX,
boundsRect.yMax * transformation.scaleY - (boundsRect.yMax - scalingGrid.yMax)
);
sourceRect[6] = new ExportRectangle(0, scalingGrid.yMax, scalingGrid.xMin, boundsRect.yMax);
targetRect[6] = new ExportRectangle(0, boundsRect.yMax * transformation.scaleY - (boundsRect.yMax - scalingGrid.yMax), scalingGrid.xMin, boundsRect.yMax * transformation.scaleY);
sourceRect[7] = new ExportRectangle(scalingGrid.xMin, scalingGrid.yMax, scalingGrid.xMax, boundsRect.yMax);
targetRect[7] = new ExportRectangle(scalingGrid.xMin, boundsRect.yMax * transformation.scaleY - (boundsRect.yMax - scalingGrid.yMax), boundsRect.xMax * transformation.scaleX - (boundsRect.xMax - scalingGrid.xMax), boundsRect.yMax * transformation.scaleY);
sourceRect[8] = new ExportRectangle(scalingGrid.xMax, scalingGrid.yMax, boundsRect.xMax, boundsRect.yMax);
targetRect[8] = new ExportRectangle(
boundsRect.xMax * transformation.scaleX - (boundsRect.xMax - scalingGrid.xMax),
boundsRect.yMax * transformation.scaleY - (boundsRect.yMax - scalingGrid.yMax),
boundsRect.xMax * transformation.scaleX,
boundsRect.yMax * transformation.scaleY);
for (int i = 0; i < targetRect.length; i++) {
targetRect[i] = prevScale.transform(targetRect[i]);
//targetRect[i] = absDiffTransform.transform(targetRect[i]);
transforms[i] = rectToRectMatrix(sourceRect[i], targetRect[i]);
//Round to pixel boundary
targetRect[i].xMax = Math.rint(targetRect[i].xMax / SWF.unitDivisor);
targetRect[i].yMax = Math.rint(targetRect[i].yMax / SWF.unitDivisor);
targetRect[i].xMin = Math.rint(targetRect[i].xMin / SWF.unitDivisor);
targetRect[i].yMin = Math.rint(targetRect[i].yMin / SWF.unitDivisor);
}
for (int i = 0; i < targetRect.length; i++) {
toImage(frame, time, renderContext, image, isClip, transforms[i], absoluteTransformation, colorTransform, targetRect[i]);
}
}*/
private void drawDrawable(Matrix strokeTransform, DepthState layer, Matrix layerMatrix, Graphics2D g, ColorTransform colorTransForm, int blendMode, List<Clip> clips, List<Shape> prevClips, Matrix transformation, boolean isClip, int clipDepth, Matrix absMat, int time, int ratio, RenderContext renderContext, SerializableImage image, DrawableTag drawable, List<FILTER> filters, double unzoom, ColorTransform clrTrans) {
Matrix drawMatrix = new Matrix();
int drawableFrameCount = drawable.getNumFrames();
if (drawableFrameCount == 0) {
drawableFrameCount = 1;
}
RECT boundRect = drawable.getRect();
ExportRectangle rect = new ExportRectangle(boundRect);
Matrix mat = transformation.concatenate(layerMatrix);
rect = mat.transform(rect);
Matrix m = mat.clone();
if (filters != null && filters.size() > 0) {
// calculate size after applying the filters
double deltaXMax = 0;
double deltaYMax = 0;
for (FILTER filter : filters) {
double x = filter.getDeltaX();
double y = filter.getDeltaY();
deltaXMax = Math.max(x, deltaXMax);
deltaYMax = Math.max(y, deltaYMax);
}
rect.xMin -= deltaXMax * unzoom;
rect.xMax += deltaXMax * unzoom;
rect.yMin -= deltaYMax * unzoom;
rect.yMax += deltaYMax * unzoom;
}
rect.xMin -= 1 * unzoom;
rect.yMin -= 1 * unzoom;
rect.xMin = Math.max(0, rect.xMin);
rect.yMin = Math.max(0, rect.yMin);
int newWidth = (int) (rect.getWidth() / unzoom);
int newHeight = (int) (rect.getHeight() / unzoom);
int deltaX = (int) (rect.xMin / unzoom);
int deltaY = (int) (rect.yMin / unzoom);
newWidth = Math.min(image.getWidth() - deltaX, newWidth) + 1;
newHeight = Math.min(image.getHeight() - deltaY, newHeight) + 1;
if (newWidth <= 0 || newHeight <= 0) {
return;
}
m.translate(-rect.xMin, -rect.yMin);
//strokeTransform = strokeTransform.clone();
//strokeTransform.translate(-rect.xMin, -rect.yMin);
drawMatrix.translate(rect.xMin, rect.yMin);
SerializableImage img = null;
String cacheKey = null;
if (drawable instanceof ShapeTag) {
cacheKey = ((ShapeTag) drawable).getCharacterId() + m.toString() + (clrTrans == null ? "" : clrTrans.toString());
//img = renderContext.shapeCache.get(cacheKey);
}
int dframe;
if (fontFrameNum != -1) {
dframe = fontFrameNum;
} else {
dframe = time % drawableFrameCount;
}
if (drawable instanceof ButtonTag) {
dframe = ButtonTag.FRAME_UP;
if (renderContext.cursorPosition != null) {
Shape buttonShape = drawable.getOutline(ButtonTag.FRAME_HITTEST, time, ratio, renderContext, absMat, true);
if (buttonShape.contains(renderContext.cursorPosition)) {
renderContext.mouseOverButton = (ButtonTag) drawable;
if (renderContext.mouseButton > 0) {
dframe = ButtonTag.FRAME_DOWN;
} else {
dframe = ButtonTag.FRAME_OVER;
}
}
}
}
int stateCount = renderContext.stateUnderCursor == null ? 0 : renderContext.stateUnderCursor.size();
if (img == null) {
img = new SerializableImage(newWidth, newHeight, SerializableImage.TYPE_INT_ARGB);
img.fillTransparent();
drawable.toImage(dframe, time, ratio, renderContext, img, isClip || clipDepth > -1, m, strokeTransform, absMat, clrTrans);
if (cacheKey != null) {
renderContext.shapeCache.put(cacheKey, img);
}
}
if (filters != null) {
for (FILTER filter : filters) {
img = filter.apply(img);
}
}
if (blendMode > 1) {
if (colorTransForm != null) {
img = colorTransForm.apply(img);
}
}
drawMatrix.translateX /= unzoom;
drawMatrix.translateY /= unzoom;
AffineTransform trans = drawMatrix.toTransform();
switch (blendMode) {
case 0:
case 1:
g.setComposite(AlphaComposite.SrcOver);
break;
case 2: // Layer
g.setComposite(AlphaComposite.SrcOver);
break;
case 3:
g.setComposite(BlendComposite.Multiply);
break;
case 4:
g.setComposite(BlendComposite.Screen);
break;
case 5:
g.setComposite(BlendComposite.Lighten);
break;
case 6:
g.setComposite(BlendComposite.Darken);
break;
case 7:
g.setComposite(BlendComposite.Difference);
break;
case 8:
g.setComposite(BlendComposite.Add);
break;
case 9:
g.setComposite(BlendComposite.Subtract);
break;
case 10:
g.setComposite(BlendComposite.Invert);
break;
case 11:
g.setComposite(BlendComposite.Alpha);
break;
case 12:
g.setComposite(BlendComposite.Erase);
break;
case 13:
g.setComposite(BlendComposite.Overlay);
break;
case 14:
g.setComposite(BlendComposite.HardLight);
break;
default: // Not implemented
g.setComposite(AlphaComposite.SrcOver);
break;
}
if (clipDepth > -1) {
BufferedImage mask = new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
Graphics2D gm = (Graphics2D) mask.getGraphics();
gm.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
gm.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
gm.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
gm.setComposite(AlphaComposite.Src);
gm.setColor(new Color(0, 0, 0, 0f));
gm.fillRect(0, 0, image.getWidth(), image.getHeight());
gm.setTransform(trans);
gm.drawImage(img.getBufferedImage(), 0, 0, null);
Clip clip = new Clip(Helper.imageToShape(mask), clipDepth); // Maybe we can get current outline instead converting from image (?)
clips.add(clip);
prevClips.add(g.getClip());
g.setTransform(AffineTransform.getTranslateInstance(0, 0));
g.setClip(clip.shape);
} else {
if (renderContext.cursorPosition != null) {
if (drawable instanceof DefineSpriteTag) {
if (renderContext.stateUnderCursor.size() > stateCount) {
renderContext.stateUnderCursor.add(layer);
}
} else if (absMat.transform(new ExportRectangle(boundRect)).contains(renderContext.cursorPosition)) {
Shape shape = drawable.getOutline(dframe, time, layer.ratio, renderContext, absMat, true);
if (shape.contains(renderContext.cursorPosition)) {
renderContext.stateUnderCursor.add(layer);
}
}
}
if (renderContext.borderImage != null) {
Graphics2D g2 = (Graphics2D) renderContext.borderImage.getGraphics();
g2.setPaint(Color.red);
g2.setStroke(new BasicStroke(2));
Shape shape = drawable.getOutline(dframe, time, layer.ratio, renderContext, absMat.preConcatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)), true);
g2.draw(shape);
}
g.setTransform(trans);
g.drawImage(img.getBufferedImage(), 0, 0, null);
}
}
public void toImage(int frame, int time, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix absoluteTransformation, ColorTransform colorTransform, ExportRectangle clipRect) {
public void toImage(int frame, int time, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix strokeTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) {
double unzoom = SWF.unitDivisor;
if (getFrameCount() <= frame) {
return;
@@ -660,13 +802,14 @@ public class Timeline {
Frame frameObj = getFrame(frame);
Graphics2D g = (Graphics2D) image.getGraphics();
Shape prevClip = g.getClip();
//if (targetRect != null) {
// g.setClip(new Rectangle2D.Double(targetRect.xMin, targetRect.yMin, targetRect.getWidth(), targetRect.getHeight()));
//}
g.setPaint(frameObj.backgroundColor.toColor());
g.fill(new Rectangle(image.getWidth(), image.getHeight()));
Shape prevClip = g.getClip();
if (clipRect != null) {
g.setClip(new Rectangle2D.Double(clipRect.xMin, clipRect.yMin, clipRect.getWidth(), clipRect.getHeight()));
}
g.setTransform(transformation.toTransform());
List<Clip> clips = new ArrayList<>();
List<Shape> prevClips = new ArrayList<>();
@@ -703,210 +846,52 @@ public class Timeline {
boolean showPlaceholder = false;
if (character instanceof DrawableTag) {
DrawableTag drawable = (DrawableTag) character;
Matrix drawMatrix = new Matrix();
int drawableFrameCount = drawable.getNumFrames();
if (drawableFrameCount == 0) {
drawableFrameCount = 1;
RECT scalingRect = null;
DefineScalingGridTag sgt = character.getScalingGridTag();
if (sgt != null) {
scalingRect = sgt.splitter;
}
RECT boundRect = drawable.getRect();
ExportRectangle rect = new ExportRectangle(boundRect);
rect = mat.transform(rect);
Matrix m = mat.clone();
if (layer.filters != null && layer.filters.size() > 0) {
// calculate size after applying the filters
double deltaXMax = 0;
double deltaYMax = 0;
for (FILTER filter : layer.filters) {
double x = filter.getDeltaX();
double y = filter.getDeltaY();
deltaXMax = Math.max(x, deltaXMax);
deltaYMax = Math.max(y, deltaYMax);
if (scalingRect != null) {
ExportRectangle sourceRect[] = new ExportRectangle[9];
ExportRectangle targetRect[] = new ExportRectangle[9];
Matrix transforms[] = new Matrix[9];
//mat => image
//t =>
//Matrix tx = transformation.concatenate(layerMatrix);
DrawableTag dr = (DrawableTag) character;
ExportRectangle boundsRect = new ExportRectangle(dr.getRect());
ExportRectangle targetBoundsRect = layerMatrix.transform(boundsRect);
DefineScalingGridTag.getSlices(targetBoundsRect, boundsRect, new ExportRectangle(scalingRect), sourceRect, targetRect, transforms);
Shape c = g.getClip();
AffineTransform origTransform = g.getTransform();
for (int s = 0; s < 9; s++) {
g.setTransform(new AffineTransform());
ExportRectangle p1 = transformation.transform(targetRect[s]);
g.setClip(c);
Rectangle2D r = new Rectangle2D.Double(p1.xMin, p1.yMin, p1.getWidth(), p1.getHeight());
g.setClip(r);
drawDrawable(strokeTransformation.preConcatenate(layerMatrix), layer, transforms[s], g, colorTransform, layer.blendMode, clips, prevClips, transformation.clone(), isClip, layer.clipDepth, absMat, layer.time, layer.ratio, renderContext, image, (DrawableTag) character, layer.filters, unzoom, clrTrans);
}
rect.xMin -= deltaXMax * unzoom;
rect.xMax += deltaXMax * unzoom;
rect.yMin -= deltaYMax * unzoom;
rect.yMax += deltaYMax * unzoom;
}
g.setClip(c);
rect.xMin -= 1 * unzoom;
rect.yMin -= 1 * unzoom;
rect.xMin = Math.max(0, rect.xMin);
rect.yMin = Math.max(0, rect.yMin);
/*
for (int s = 0; s < 9; s++) {
g.setTransform(new AffineTransform());
ExportRectangle p1 = transformation.transform(targetRect[s]);
g.setClip(c);
int newWidth = (int) (rect.getWidth() / unzoom);
int newHeight = (int) (rect.getHeight() / unzoom);
int deltaX = (int) (rect.xMin / unzoom);
int deltaY = (int) (rect.yMin / unzoom);
newWidth = Math.min(image.getWidth() - deltaX, newWidth) + 1;
newHeight = Math.min(image.getHeight() - deltaY, newHeight) + 1;
Rectangle2D r = new Rectangle2D.Double(p1.xMin, p1.yMin, p1.getWidth(), p1.getHeight());
g.setColor(Color.blue);
g.draw(r);
if (newWidth <= 0 || newHeight <= 0) {
continue;
}
m.translate(-rect.xMin, -rect.yMin);
drawMatrix.translate(rect.xMin, rect.yMin);
SerializableImage img = null;
String cacheKey = null;
if (drawable instanceof ShapeTag) {
cacheKey = ((ShapeTag) drawable).getCharacterId() + m.toString() + (clrTrans == null ? "" : clrTrans.toString());
img = renderContext.shapeCache.get(cacheKey);
}
int dframe;
if (fontFrameNum != -1) {
dframe = fontFrameNum;
}*/
g.setTransform(origTransform);
} else {
dframe = time % drawableFrameCount;
}
if (character instanceof ButtonTag) {
dframe = ButtonTag.FRAME_UP;
if (renderContext.cursorPosition != null) {
Shape buttonShape = drawable.getOutline(ButtonTag.FRAME_HITTEST, time, layer.ratio, renderContext, absMat);
if (buttonShape.contains(renderContext.cursorPosition)) {
renderContext.mouseOverButton = (ButtonTag) character;
if (renderContext.mouseButton > 0) {
dframe = ButtonTag.FRAME_DOWN;
} else {
dframe = ButtonTag.FRAME_OVER;
}
}
}
}
int stateCount = renderContext.stateUnderCursor == null ? 0 : renderContext.stateUnderCursor.size();
if (img == null) {
img = new SerializableImage(newWidth, newHeight, SerializableImage.TYPE_INT_ARGB);
img.fillTransparent();
drawable.toImage(dframe, time, layer.ratio, renderContext, img, isClip || layer.clipDepth > -1, m, transformation, absMat, clrTrans);
if (cacheKey != null) {
renderContext.shapeCache.put(cacheKey, img);
}
}
/*//if (renderContext.stateUnderCursor == layer) {
if (true) {
BufferedImage bi = img.getBufferedImage();
ColorModel cm = bi.getColorModel();
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
WritableRaster raster = bi.copyData(null);
img = new SerializableImage(new BufferedImage(cm, raster, isAlphaPremultiplied, null));
Graphics2D gg = (Graphics2D) img.getGraphics();
gg.setStroke(new BasicStroke(3));
gg.setPaint(Color.red);
gg.setTransform(AffineTransform.getTranslateInstance(0, 0));
gg.draw(SHAPERECORD.twipToPixelShape(drawable.getOutline(dframe, layer.time + time, layer.ratio, renderContext, m)));
}*/
if (layer.filters != null) {
for (FILTER filter : layer.filters) {
img = filter.apply(img);
}
}
if (layer.blendMode > 1) {
if (layer.colorTransForm != null) {
img = layer.colorTransForm.apply(img);
}
}
drawMatrix.translateX /= unzoom;
drawMatrix.translateY /= unzoom;
AffineTransform trans = drawMatrix.toTransform();
switch (layer.blendMode) {
case 0:
case 1:
g.setComposite(AlphaComposite.SrcOver);
break;
case 2: // Layer
g.setComposite(AlphaComposite.SrcOver);
break;
case 3:
g.setComposite(BlendComposite.Multiply);
break;
case 4:
g.setComposite(BlendComposite.Screen);
break;
case 5:
g.setComposite(BlendComposite.Lighten);
break;
case 6:
g.setComposite(BlendComposite.Darken);
break;
case 7:
g.setComposite(BlendComposite.Difference);
break;
case 8:
g.setComposite(BlendComposite.Add);
break;
case 9:
g.setComposite(BlendComposite.Subtract);
break;
case 10:
g.setComposite(BlendComposite.Invert);
break;
case 11:
g.setComposite(BlendComposite.Alpha);
break;
case 12:
g.setComposite(BlendComposite.Erase);
break;
case 13:
g.setComposite(BlendComposite.Overlay);
break;
case 14:
g.setComposite(BlendComposite.HardLight);
break;
default: // Not implemented
g.setComposite(AlphaComposite.SrcOver);
break;
}
if (layer.clipDepth > -1) {
BufferedImage mask = new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
Graphics2D gm = (Graphics2D) mask.getGraphics();
gm.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
gm.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
gm.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
gm.setComposite(AlphaComposite.Src);
gm.setColor(new Color(0, 0, 0, 0f));
gm.fillRect(0, 0, image.getWidth(), image.getHeight());
gm.setTransform(trans);
gm.drawImage(img.getBufferedImage(), 0, 0, null);
Clip clip = new Clip(Helper.imageToShape(mask), layer.clipDepth); // Maybe we can get current outline instead converting from image (?)
clips.add(clip);
prevClips.add(g.getClip());
g.setTransform(AffineTransform.getTranslateInstance(0, 0));
g.setClip(clip.shape);
} else {
if (renderContext.cursorPosition != null) {
if (drawable instanceof DefineSpriteTag) {
if (renderContext.stateUnderCursor.size() > stateCount) {
renderContext.stateUnderCursor.add(layer);
}
} else if (absMat.transform(new ExportRectangle(boundRect)).contains(renderContext.cursorPosition)) {
Shape shape = drawable.getOutline(dframe, time, layer.ratio, renderContext, absMat);
if (shape.contains(renderContext.cursorPosition)) {
renderContext.stateUnderCursor.add(layer);
}
}
}
if (renderContext.borderImage != null) {
Graphics2D g2 = (Graphics2D) renderContext.borderImage.getGraphics();
g2.setPaint(Color.red);
g2.setStroke(new BasicStroke(2));
Shape shape = drawable.getOutline(dframe, time, layer.ratio, renderContext, absMat.preConcatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)));
g2.draw(shape);
}
g.setTransform(trans);
g.drawImage(img.getBufferedImage(), 0, 0, null);
drawDrawable(strokeTransformation, layer, layerMatrix, g, colorTransform, layer.blendMode, clips, prevClips, transformation.clone(), isClip, layer.clipDepth, absMat, layer.time, layer.ratio, renderContext, image, (DrawableTag) character, layer.filters, unzoom, clrTrans);
}
} else if (character instanceof BoundedTag) {
showPlaceholder = true;
@@ -1075,7 +1060,7 @@ public class Timeline {
}
}
public Shape getOutline(int frame, int time, RenderContext renderContext, Matrix transformation) {
public Shape getOutline(int frame, int time, RenderContext renderContext, Matrix transformation, boolean stroked) {
Frame fr = getFrame(frame);
Area area = new Area();
Stack<Clip> clips = new Stack<>();
@@ -1112,7 +1097,7 @@ public class Timeline {
dframe = ButtonTag.FRAME_UP;
if (renderContext.cursorPosition != null) {
ButtonTag buttonTag = (ButtonTag) character;
Shape buttonShape = buttonTag.getOutline(ButtonTag.FRAME_HITTEST, time, layer.ratio, renderContext, m);
Shape buttonShape = buttonTag.getOutline(ButtonTag.FRAME_HITTEST, time, layer.ratio, renderContext, m, stroked);
if (buttonShape.contains(renderContext.cursorPosition)) {
if (renderContext.mouseButton > 0) {
dframe = ButtonTag.FRAME_DOWN;
@@ -1123,7 +1108,7 @@ public class Timeline {
}
}
Shape cshape = ((DrawableTag) character).getOutline(dframe, time, layer.ratio, renderContext, m);
Shape cshape = ((DrawableTag) character).getOutline(dframe, time, layer.ratio, renderContext, m, stroked);
Area addArea = new Area(cshape);
if (currentClip != null) {
Area a = new Area(new Rectangle(displayRect.Xmin, displayRect.Ymin, displayRect.getWidth(), displayRect.getHeight()));

View File

@@ -77,16 +77,23 @@ public class SHAPE implements NeedsCharacters, Serializable {
return SHAPERECORD.getBounds(shapeRecords);
}
public Shape getOutline(SWF swf) {
public Shape getOutline(SWF swf, boolean stroked) {
if (cachedOutline != null) {
return cachedOutline;
}
List<GeneralPath> paths = PathExporter.export(swf, this);
List<GeneralPath> strokes = new ArrayList<>();
List<GeneralPath> paths = PathExporter.export(swf, this, strokes);
Area area = new Area();
for (GeneralPath path : paths) {
area.add(new Area(path));
}
if (stroked) {
for (GeneralPath path : strokes) {
area.add(new Area(path));
}
}
cachedOutline = area;
return area;

View File

@@ -220,7 +220,7 @@ public abstract class SHAPERECORD implements Cloneable, NeedsCharacters, Seriali
Matrix transformation = new Matrix();
transformation.translate(px, py);
transformation = transformation.concatenate(Matrix.getScaleInstance(ratio));
BitmapExporter.export(swf, shape, color, image, transformation, colorTransform);
BitmapExporter.export(swf, shape, color, image, transformation, transformation, colorTransform);
// draw bounding boxes
if (DRAW_BOUNDING_BOX) {