diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ShapeExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ShapeExporter.java index 6a5a6beac..c98317b98 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ShapeExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ShapeExporter.java @@ -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 { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/ExportRectangle.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/ExportRectangle.java index 54608cb4f..4e9b18565 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/ExportRectangle.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/ExportRectangle.java @@ -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; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/BitmapExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/BitmapExporter.java index 852cb94bc..af616d114 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/BitmapExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/BitmapExporter.java @@ -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 } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/PathExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/PathExporter.java index c5036c778..fd4d55bee 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/PathExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/PathExporter.java @@ -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 paths = new ArrayList<>(); + private final List strokes = new ArrayList<>(); + private double thickness = 0; private GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD); public static List export(SWF swf, SHAPE shape) { + return export(swf, shape, new ArrayList<>()); + } + + public static List export(SWF swf, SHAPE shape, List 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 } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgImporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgImporter.java index f51ba5b53..44a33590b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgImporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgImporter.java @@ -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 li = new ArrayList<>(); li.add(st); ImageIO.write(si.getBufferedImage(), "PNG", new File(name + ".imported.png")); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java index c260da3eb..0044caa3c 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java @@ -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); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineScalingGridTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineScalingGridTag.java index a9d11aee7..d97890a8e 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineScalingGridTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineScalingGridTag.java @@ -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 + } + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java index b501a67cd..01446b832 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java @@ -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 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java index 2b1904dc1..6149da630 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java @@ -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 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/CharacterTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/CharacterTag.java index 517437e87..e0e5409f3 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/CharacterTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/CharacterTag.java @@ -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 mtags = swf.getCharacterIdTags(getCharacterId()); + for (CharacterIdTag ct : mtags) { + if (ct instanceof DefineScalingGridTag) { + return (DefineScalingGridTag) ct; + } + } + return null; + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/DrawableTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/DrawableTag.java index 89b9e1f32..08cc3912a 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/DrawableTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/DrawableTag.java @@ -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); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/FontTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/FontTag.java index 0fd09096a..b1f094327 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/FontTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/FontTag.java @@ -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); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ImageTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ImageTag.java index fddc02f87..723a0d156 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ImageTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ImageTag.java @@ -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 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/MorphShapeTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/MorphShapeTag.java index 536de0e8b..23c6c33cd 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/MorphShapeTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/MorphShapeTag.java @@ -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 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ShapeTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ShapeTag.java index 6975be5f6..a983071ed 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ShapeTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ShapeTag.java @@ -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 paths = PathExporter.export(swf, getShapes()); double[] coords = new double[6]; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/StaticTextTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/StaticTextTag.java index 40af6bc10..bc455e0f7 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/StaticTextTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/StaticTextTag.java @@ -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(); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/TextTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/TextTag.java index bbfd715c1..933f26be7 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/TextTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/TextTag.java @@ -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 (?) diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java index f7e2635f5..966e41f97 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java @@ -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 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 clips, List prevClips, Matrix transformation, boolean isClip, int clipDepth, Matrix absMat, int time, int ratio, RenderContext renderContext, SerializableImage image, DrawableTag drawable, List 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 clips = new ArrayList<>(); List 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 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())); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/SHAPE.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/SHAPE.java index fa45695ef..0430c0f55 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/SHAPE.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/SHAPE.java @@ -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 paths = PathExporter.export(swf, this); + List strokes = new ArrayList<>(); + List 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; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/SHAPERECORD.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/SHAPERECORD.java index 9ff6fa0aa..babcf1ab5 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/SHAPERECORD.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/SHAPERECORD.java @@ -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) { diff --git a/src/com/jpexs/decompiler/flash/gui/FolderPreviewPanel.java b/src/com/jpexs/decompiler/flash/gui/FolderPreviewPanel.java index 78ef310e3..db97f4f61 100644 --- a/src/com/jpexs/decompiler/flash/gui/FolderPreviewPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/FolderPreviewPanel.java @@ -364,7 +364,7 @@ public class FolderPreviewPanel extends JPanel { image.fillTransparent(); if (imgSrc == null) { DrawableTag drawable = (DrawableTag) treeItem; - drawable.toImage(0, 0, 0, new RenderContext(), image, false, m, new Matrix(), m, null); + drawable.toImage(0, 0, 0, new RenderContext(), image, false, m, m, m, null); } else { Graphics2D g = (Graphics2D) image.getGraphics(); g.setTransform(m.toTransform()); diff --git a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java index baa7f631a..42b4d229e 100644 --- a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java @@ -654,10 +654,10 @@ public final class ImagePanel extends JPanel implements MediaDisplay { Matrix m = new Matrix(); m.translate(-rect.Xmin * zoomDouble, -rect.Ymin * zoomDouble); m.scale(zoomDouble); - textTag.toImage(0, 0, 0, new RenderContext(), image, false, m, new Matrix(), m, new ConstantColorColorTransform(0xFFC0C0C0)); + textTag.toImage(0, 0, 0, new RenderContext(), image, false, m, m, m, new ConstantColorColorTransform(0xFFC0C0C0)); if (newTextTag != null) { - newTextTag.toImage(0, 0, 0, new RenderContext(), image, false, m, new Matrix(), m, new ConstantColorColorTransform(0xFF000000)); + newTextTag.toImage(0, 0, 0, new RenderContext(), image, false, m, m, m, new ConstantColorColorTransform(0xFF000000)); } iconPanel.setImg(image); @@ -800,7 +800,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { } int dframe = time % drawableFrameCount; - Shape outline = dt.getOutline(dframe, time, ds.ratio, renderContext, Matrix.getScaleInstance(1 / SWF.unitDivisor).concatenate(m.concatenate(new Matrix(ds.matrix)))); + Shape outline = dt.getOutline(dframe, time, ds.ratio, renderContext, Matrix.getScaleInstance(1 / SWF.unitDivisor).concatenate(m.concatenate(new Matrix(ds.matrix))), true); Rectangle bounds = outline.getBounds(); gg.setStroke(new BasicStroke(2.0f, BasicStroke.CAP_BUTT, @@ -991,7 +991,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { ret.append(" - "); } - ButtonTag lastMouseOverButton = null; + ButtonTag lastMouseOverButton; synchronized (ImagePanel.class) { if (timer == thisTimer) { iconPanel.setImg(img);