From c3bf4963b0941e57603c33dc8a39e96e123b29e7 Mon Sep 17 00:00:00 2001 From: Honfika Date: Thu, 17 Apr 2014 00:34:39 +0200 Subject: [PATCH] #559 morphshapes as animated SVG/CSS3 (Currenly only the edges are morphed, the style is not ready) --- trunk/src/com/jpexs/decompiler/flash/SWF.java | 71 ++- .../flash/exporters/BitmapExporter.java | 4 +- .../flash/exporters/CurvedMorphEdge.java | 47 ++ .../DefaultSVGMorphShapeExporter.java | 150 +++++ .../exporters/DefaultSVGShapeExporter.java | 8 +- .../flash/exporters/IMorphEdge.java | 38 ++ .../flash/exporters/IMorphShapeExporter.java | 58 ++ .../flash/exporters/IShapeExporter.java | 2 +- .../exporters/MorphShapeExporterBase.java | 546 ++++++++++++++++++ .../flash/exporters/PathExporter.java | 2 +- .../exporters/SVGMorphShapeExporter.java | 353 +++++++++++ .../flash/exporters/SVGShapeExporter.java | 18 +- .../flash/exporters/ShapeExporterBase.java | 93 +-- .../flash/exporters/StraightMorphEdge.java | 75 +++ .../exporters/modes/MorphshapeExportMode.java | 9 +- .../decompiler/flash/gui/ExportDialog.java | 14 +- .../jpexs/decompiler/flash/gui/MainPanel.java | 12 +- .../flash/gui/locales/ExportDialog.properties | 1 + .../flash/tags/DefineMorphShape2Tag.java | 12 + .../flash/tags/DefineMorphShapeTag.java | 12 + .../flash/tags/DefineShape2Tag.java | 4 +- .../flash/tags/DefineShape3Tag.java | 4 +- .../flash/tags/DefineShape4Tag.java | 4 +- .../decompiler/flash/tags/DefineShapeTag.java | 4 +- .../flash/tags/base/MorphShapeTag.java | 2 + 25 files changed, 1425 insertions(+), 118 deletions(-) create mode 100644 trunk/src/com/jpexs/decompiler/flash/exporters/CurvedMorphEdge.java create mode 100644 trunk/src/com/jpexs/decompiler/flash/exporters/DefaultSVGMorphShapeExporter.java create mode 100644 trunk/src/com/jpexs/decompiler/flash/exporters/IMorphEdge.java create mode 100644 trunk/src/com/jpexs/decompiler/flash/exporters/IMorphShapeExporter.java create mode 100644 trunk/src/com/jpexs/decompiler/flash/exporters/MorphShapeExporterBase.java create mode 100644 trunk/src/com/jpexs/decompiler/flash/exporters/SVGMorphShapeExporter.java create mode 100644 trunk/src/com/jpexs/decompiler/flash/exporters/StraightMorphEdge.java diff --git a/trunk/src/com/jpexs/decompiler/flash/SWF.java b/trunk/src/com/jpexs/decompiler/flash/SWF.java index d9a288c09..7bc2540ee 100644 --- a/trunk/src/com/jpexs/decompiler/flash/SWF.java +++ b/trunk/src/com/jpexs/decompiler/flash/SWF.java @@ -99,6 +99,7 @@ import com.jpexs.decompiler.flash.tags.base.ContainerItem; import com.jpexs.decompiler.flash.tags.base.DrawableTag; import com.jpexs.decompiler.flash.tags.base.FontTag; import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; import com.jpexs.decompiler.flash.tags.base.RemoveTag; import com.jpexs.decompiler.flash.tags.base.ShapeTag; @@ -1479,24 +1480,6 @@ public final class SWF implements TreeItem, Timelined { fos.write(chunkBytes); } - //TODO: implement morphshape export. How to handle 65536 frames? - public List exportMorphShapes(AbortRetryIgnoreHandler handler, String outdir, List tags, final MorphshapeExportMode mode) throws IOException { - List ret = new ArrayList<>(); - if (tags.isEmpty()) { - return ret; - } - File foutdir = new File(outdir); - if (!foutdir.exists()) { - if (!foutdir.mkdirs()) { - if (!foutdir.exists()) { - throw new IOException("Cannot create directory " + outdir); - } - } - } - throw new UnsupportedOperationException("Not implemented"); - //return ret; - } - private static void makeAVI(List images, int frameRate, File file) throws IOException { if (images.isEmpty()) { return; @@ -1840,7 +1823,7 @@ public final class SWF implements TreeItem, Timelined { } } } - loopb: + for (final Tag t : tags) { if (t instanceof ShapeTag) { int characterID = 0; @@ -1884,6 +1867,50 @@ public final class SWF implements TreeItem, Timelined { return ret; } + //TODO: implement morphshape export. How to handle 65536 frames? + public static List exportMorphShapes(AbortRetryIgnoreHandler handler, String outdir, List tags, final MorphshapeExportMode mode) throws IOException { + List ret = new ArrayList<>(); + if (tags.isEmpty()) { + return ret; + } + File foutdir = new File(outdir); + if (!foutdir.exists()) { + if (!foutdir.mkdirs()) { + if (!foutdir.exists()) { + throw new IOException("Cannot create directory " + outdir); + } + } + } + + for (final Tag t : tags) { + if (t instanceof MorphShapeTag) { + int characterID = 0; + if (t instanceof CharacterTag) { + characterID = ((CharacterTag) t).getCharacterId(); + } + String ext = "svg"; + + final File file = new File(outdir + File.separator + characterID + "." + ext); + new RetryTask(new RunnableIOEx() { + @Override + public void run() throws IOException { + MorphShapeTag mst = (MorphShapeTag) t; + switch (mode) { + case SVG: + try (FileOutputStream fos = new FileOutputStream(file)) { + fos.write(Utf8Helper.getBytes(mst.toSVG())); + } + break; + } + + } + }, handler).run(); + ret.add(file); + } + } + return ret; + } + public static List exportBinaryData(AbortRetryIgnoreHandler handler, String outdir, List tags, BinaryDataExportMode mode) throws IOException { List ret = new ArrayList<>(); if (tags.isEmpty()) { @@ -1897,7 +1924,7 @@ public final class SWF implements TreeItem, Timelined { } } } - loopb: + for (final Tag t : tags) { if (t instanceof DefineBinaryDataTag) { int characterID = ((DefineBinaryDataTag) t).getCharacterId(); @@ -1964,6 +1991,10 @@ public final class SWF implements TreeItem, Timelined { exportShapes(handler, outdir, tags, mode); } + public void exportMorphShapes(AbortRetryIgnoreHandler handler, String outdir, MorphshapeExportMode mode) throws IOException { + exportMorphShapes(handler, outdir, tags, mode); + } + public void exportBinaryData(AbortRetryIgnoreHandler handler, String outdir, BinaryDataExportMode mode) throws IOException { exportBinaryData(handler, outdir, tags, mode); } diff --git a/trunk/src/com/jpexs/decompiler/flash/exporters/BitmapExporter.java b/trunk/src/com/jpexs/decompiler/flash/exporters/BitmapExporter.java index 04f804782..8ebef0955 100644 --- a/trunk/src/com/jpexs/decompiler/flash/exporters/BitmapExporter.java +++ b/trunk/src/com/jpexs/decompiler/flash/exporters/BitmapExporter.java @@ -46,7 +46,7 @@ import java.util.List; * * @author JPEXS */ -public class BitmapExporter extends ShapeExporterBase implements IShapeExporter { +public class BitmapExporter extends ShapeExporterBase { private SerializableImage image; private Graphics2D graphics; @@ -94,7 +94,7 @@ public class BitmapExporter extends ShapeExporterBase implements IShapeExporter } @Override - public void endShape(double xMin, double yMin, double xMax, double yMax) { + public void endShape() { } @Override diff --git a/trunk/src/com/jpexs/decompiler/flash/exporters/CurvedMorphEdge.java b/trunk/src/com/jpexs/decompiler/flash/exporters/CurvedMorphEdge.java new file mode 100644 index 000000000..1987cc030 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/exporters/CurvedMorphEdge.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.exporters; + +/** + * + * @author JPEXS + */ +public class CurvedMorphEdge extends StraightMorphEdge implements IMorphEdge { + + private final PointInt control; + private final PointInt controlEnd; + + CurvedMorphEdge(PointInt from, PointInt control, PointInt to, + PointInt fromEnd, PointInt controlEnd, PointInt toEnd, int lineStyleIdx, int fillStyleIdx) { + super(from, to, fromEnd, toEnd, lineStyleIdx, fillStyleIdx); + this.control = control; + this.controlEnd = controlEnd; + } + + public PointInt getControl() { + return control; + } + + public PointInt getControlEnd() { + return controlEnd; + } + + @Override + public IMorphEdge reverseWithNewFillStyle(int newFillStyleIdx) { + return new CurvedMorphEdge(to, control, from, toEnd, controlEnd, fromEnd, lineStyleIdx, newFillStyleIdx); + } +} diff --git a/trunk/src/com/jpexs/decompiler/flash/exporters/DefaultSVGMorphShapeExporter.java b/trunk/src/com/jpexs/decompiler/flash/exporters/DefaultSVGMorphShapeExporter.java new file mode 100644 index 000000000..1797d1f1a --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/exporters/DefaultSVGMorphShapeExporter.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.exporters; + +import com.jpexs.decompiler.flash.SWF; +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; + +/** + * + * @author JPEXS, Claus Wahlers + */ +public abstract class DefaultSVGMorphShapeExporter extends MorphShapeExporterBase { + + protected static final String DRAW_COMMAND_L = "L"; + protected static final String DRAW_COMMAND_Q = "Q"; + + protected String currentDrawCommand = ""; + protected String pathData; + protected String pathDataEnd; + + public DefaultSVGMorphShapeExporter(SHAPE shape, SHAPE endShape, ColorTransform colorTransform) { + super(shape, endShape, colorTransform); + } + + @Override + public void beginShape() { + } + + @Override + public void endShape() { + } + + @Override + public void beginFills() { + } + + @Override + public void endFills() { + } + + @Override + public void beginLines() { + } + + @Override + public void endLines() { + finalizePath(); + } + + @Override + public void beginFill(RGB color) { + finalizePath(); + } + + @Override + public void beginGradientFill(int type, GRADRECORD[] gradientRecords, Matrix matrix, int spreadMethod, int interpolationMethod, float focalPointRatio) { + finalizePath(); + } + + @Override + public void beginBitmapFill(int bitmapId, Matrix matrix, boolean repeat, boolean smooth, ColorTransform colorTransform) { + finalizePath(); + } + + @Override + public void endFill() { + finalizePath(); + } + + @Override + public void lineStyle(double thickness, RGB color, boolean pixelHinting, String scaleMode, int startCaps, int endCaps, int joints, int miterLimit) { + finalizePath(); + } + + @Override + public void lineGradientStyle(int type, GRADRECORD[] gradientRecords, Matrix matrix, int spreadMethod, int interpolationMethod, float focalPointRatio) { + } + + @Override + public void moveTo(double x, double y, double x2, double y2) { + currentDrawCommand = ""; + pathData += "M" + + roundPixels20(x / SWF.unitDivisor) + " " + + roundPixels20(y / SWF.unitDivisor) + " "; + pathDataEnd += "M" + + roundPixels20(x2 / SWF.unitDivisor) + " " + + roundPixels20(y2 / SWF.unitDivisor) + " "; + } + + @Override + public void lineTo(double x, double y, double x2, double y2) { + if (!currentDrawCommand.equals(DRAW_COMMAND_L)) { + currentDrawCommand = DRAW_COMMAND_L; + pathData += "L"; + pathDataEnd += "L"; + } + pathData += roundPixels20(x / SWF.unitDivisor) + " " + + roundPixels20(y / SWF.unitDivisor) + " "; + pathDataEnd += roundPixels20(x2 / SWF.unitDivisor) + " " + + roundPixels20(y2 / SWF.unitDivisor) + " "; + } + + @Override + public void curveTo(double controlX, double controlY, double anchorX, double anchorY, double controlX2, double controlY2, double anchorX2, double anchorY2) { + if (!currentDrawCommand.equals(DRAW_COMMAND_Q)) { + currentDrawCommand = DRAW_COMMAND_Q; + pathData += "Q"; + pathDataEnd += "Q"; + } + pathData += roundPixels20(controlX / SWF.unitDivisor) + " " + + roundPixels20(controlY / SWF.unitDivisor) + " " + + roundPixels20(anchorX / SWF.unitDivisor) + " " + + roundPixels20(anchorY / SWF.unitDivisor) + " "; + pathDataEnd += roundPixels20(controlX2 / SWF.unitDivisor) + " " + + roundPixels20(controlY2 / SWF.unitDivisor) + " " + + roundPixels20(anchorX2 / SWF.unitDivisor) + " " + + roundPixels20(anchorY2 / SWF.unitDivisor) + " "; + } + + protected void finalizePath() { + pathData = ""; + pathDataEnd = ""; + currentDrawCommand = ""; + } + + protected double roundPixels20(double pixels) { + return Math.round(pixels * 100) / 100.0; + } + + protected double roundPixels400(double pixels) { + return Math.round(pixels * 10000) / 10000.0; + } +} diff --git a/trunk/src/com/jpexs/decompiler/flash/exporters/DefaultSVGShapeExporter.java b/trunk/src/com/jpexs/decompiler/flash/exporters/DefaultSVGShapeExporter.java index 8a60b8646..581b7a90d 100644 --- a/trunk/src/com/jpexs/decompiler/flash/exporters/DefaultSVGShapeExporter.java +++ b/trunk/src/com/jpexs/decompiler/flash/exporters/DefaultSVGShapeExporter.java @@ -26,7 +26,7 @@ import com.jpexs.decompiler.flash.types.SHAPE; * * @author JPEXS, Claus Wahlers */ -public class DefaultSVGShapeExporter extends ShapeExporterBase implements IShapeExporter { +public abstract class DefaultSVGShapeExporter extends ShapeExporterBase { protected static final String DRAW_COMMAND_L = "L"; protected static final String DRAW_COMMAND_Q = "Q"; @@ -43,7 +43,7 @@ public class DefaultSVGShapeExporter extends ShapeExporterBase implements IShape } @Override - public void endShape(double xMin, double yMin, double xMax, double yMax) { + public void endShape() { } @Override @@ -102,7 +102,7 @@ public class DefaultSVGShapeExporter extends ShapeExporterBase implements IShape @Override public void lineTo(double x, double y) { - if (currentDrawCommand != DRAW_COMMAND_L) { + if (!currentDrawCommand.equals(DRAW_COMMAND_L)) { currentDrawCommand = DRAW_COMMAND_L; pathData += "L"; } @@ -112,7 +112,7 @@ public class DefaultSVGShapeExporter extends ShapeExporterBase implements IShape @Override public void curveTo(double controlX, double controlY, double anchorX, double anchorY) { - if (currentDrawCommand != DRAW_COMMAND_Q) { + if (!currentDrawCommand.equals(DRAW_COMMAND_Q)) { currentDrawCommand = DRAW_COMMAND_Q; pathData += "Q"; } diff --git a/trunk/src/com/jpexs/decompiler/flash/exporters/IMorphEdge.java b/trunk/src/com/jpexs/decompiler/flash/exporters/IMorphEdge.java new file mode 100644 index 000000000..96e5176f8 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/exporters/IMorphEdge.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.exporters; + +/** + * + * @author JPEXS + */ +public interface IMorphEdge { + + public PointInt getFrom(); + + public PointInt getTo(); + + public PointInt getFromEnd(); + + public PointInt getToEnd(); + + public int getLineStyleIdx(); + + public int getFillStyleIdx(); + + public IMorphEdge reverseWithNewFillStyle(int newFillStyleIdx); +} diff --git a/trunk/src/com/jpexs/decompiler/flash/exporters/IMorphShapeExporter.java b/trunk/src/com/jpexs/decompiler/flash/exporters/IMorphShapeExporter.java new file mode 100644 index 000000000..3dc28fabc --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/exporters/IMorphShapeExporter.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.exporters; + +import com.jpexs.decompiler.flash.types.ColorTransform; +import com.jpexs.decompiler.flash.types.GRADRECORD; +import com.jpexs.decompiler.flash.types.RGB; + +/** + * + * @author JPEXS + */ +public interface IMorphShapeExporter { + + public void beginShape(); + + public void endShape(); + + public void beginFills(); + + public void endFills(); + + public void beginLines(); + + public void endLines(); + + public void beginFill(RGB color); + + public void beginGradientFill(int type, GRADRECORD[] gradientRecords, Matrix matrix, int spreadMethod, int interpolationMethod, float focalPointRatio); + + public void beginBitmapFill(int bitmapId, Matrix matrix, boolean repeat, boolean smooth, ColorTransform colorTransform); + + public void endFill(); + + public void lineStyle(double thickness, RGB color, boolean pixelHinting, String scaleMode, int startCaps, int endCaps, int joints, int miterLimit); + + public void lineGradientStyle(int type, GRADRECORD[] gradientRecords, Matrix matrix, int spreadMethod, int interpolationMethod, float focalPointRatio); + + public void moveTo(double x, double y, double x2, double y2); + + public void lineTo(double x, double y, double x2, double y2); + + public void curveTo(double controlX, double controlY, double anchorX, double anchorY, double controlX2, double controlY2, double anchorX2, double anchorY2); +} diff --git a/trunk/src/com/jpexs/decompiler/flash/exporters/IShapeExporter.java b/trunk/src/com/jpexs/decompiler/flash/exporters/IShapeExporter.java index 843f81dd3..3e2c4f216 100644 --- a/trunk/src/com/jpexs/decompiler/flash/exporters/IShapeExporter.java +++ b/trunk/src/com/jpexs/decompiler/flash/exporters/IShapeExporter.java @@ -28,7 +28,7 @@ public interface IShapeExporter { public void beginShape(); - public void endShape(double xMin, double yMin, double xMax, double yMax); + public void endShape(); public void beginFills(); diff --git a/trunk/src/com/jpexs/decompiler/flash/exporters/MorphShapeExporterBase.java b/trunk/src/com/jpexs/decompiler/flash/exporters/MorphShapeExporterBase.java new file mode 100644 index 000000000..ce9f4806b --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/exporters/MorphShapeExporterBase.java @@ -0,0 +1,546 @@ +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.exporters; + +import com.jpexs.decompiler.flash.types.ColorTransform; +import com.jpexs.decompiler.flash.types.FILLSTYLE; +import com.jpexs.decompiler.flash.types.FOCALGRADIENT; +import com.jpexs.decompiler.flash.types.LINESTYLE; +import com.jpexs.decompiler.flash.types.LINESTYLE2; +import com.jpexs.decompiler.flash.types.RGB; +import com.jpexs.decompiler.flash.types.SHAPE; +import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE; +import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord; +import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord; +import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; +import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord; +import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord; +import java.awt.Color; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * + * @author JPEXS, Claus Wahlers + */ +public abstract class MorphShapeExporterBase implements IMorphShapeExporter { + + protected final SHAPE shape; + protected final SHAPE shapeEnd; + + protected List _fillStyles; + protected List _lineStyles; + + protected List _fillStylesEnd; + protected List _lineStylesEnd; + + protected List>> _fillEdgeMaps; + protected List>> _lineEdgeMaps; + + private boolean edgeMapsCreated; + protected ColorTransform colorTransform; + + public MorphShapeExporterBase(SHAPE shape, SHAPE endShape, ColorTransform colorTransform) { + this.shape = shape; + this.shapeEnd = endShape; + this.colorTransform = colorTransform; + _fillStyles = new ArrayList<>(); + _lineStyles = new ArrayList<>(); + if (shape instanceof SHAPEWITHSTYLE) { + SHAPEWITHSTYLE shapeWithStyle = (SHAPEWITHSTYLE) shape; + _fillStyles.addAll(Arrays.asList(shapeWithStyle.fillStyles.fillStyles)); + _lineStyles.addAll(Arrays.asList(shapeWithStyle.lineStyles.lineStyles)); + } + _fillStylesEnd = new ArrayList<>(); + _lineStylesEnd = new ArrayList<>(); + if (endShape instanceof SHAPEWITHSTYLE) { + SHAPEWITHSTYLE shapeWithStyle = (SHAPEWITHSTYLE) endShape; + _fillStylesEnd.addAll(Arrays.asList(shapeWithStyle.fillStyles.fillStyles)); + _lineStylesEnd.addAll(Arrays.asList(shapeWithStyle.lineStyles.lineStyles)); + } + } + + public void export() { + // Create edge maps + _fillEdgeMaps = new ArrayList<>(); + _lineEdgeMaps = new ArrayList<>(); + createEdgeMaps(_fillStyles, _lineStyles, _fillEdgeMaps, _lineEdgeMaps); + + // Let the doc handler know that a shape export starts + beginShape(); + // Export fills and strokes for each group separately + for (int i = 0; i < _lineEdgeMaps.size(); i++) { + // Export fills first + exportFillPath(i); + // Export strokes last + exportLinePath(i); + } + // Let the doc handler know that we're done exporting a shape + endShape(); + } + + protected void createEdgeMaps(List fillStyles, List lineStyles, + List>> fillEdgeMaps, List>> lineEdgeMaps) { + if (!edgeMapsCreated) { + int xPos = 0; + int yPos = 0; + int xPosEnd = 0; + int yPosEnd = 0; + int fillStyleIdxOffset = 0; + int lineStyleIdxOffset = 0; + int currentFillStyleIdx0 = 0; + int currentFillStyleIdx1 = 0; + int currentLineStyleIdx = 0; + List subPath = new ArrayList<>(); + Map> currentFillEdgeMap = new HashMap<>(); + Map> currentLineEdgeMap = new HashMap<>(); + List records = shape.shapeRecords; + List recordsEnd = shapeEnd.shapeRecords; + if (records.size() != recordsEnd.size()) { + throw new Error("Begin and end shaperecord list length should be the same."); + } + for (int i = 0; i < records.size(); i++) { + SHAPERECORD shapeRecord = records.get(i); + SHAPERECORD shapeRecordEnd = recordsEnd.get(i); + if ((shapeRecord instanceof StyleChangeRecord && !(shapeRecordEnd instanceof StyleChangeRecord)) || + (shapeRecord instanceof StraightEdgeRecord && !(shapeRecordEnd instanceof StraightEdgeRecord)) || + (shapeRecord instanceof CurvedEdgeRecord && !(shapeRecordEnd instanceof CurvedEdgeRecord)) || + (shapeRecord instanceof EndShapeRecord && !(shapeRecordEnd instanceof EndShapeRecord))) { + throw new Error("Begin and end shaperecord should have the same type."); + } + if (shapeRecord instanceof StyleChangeRecord) { + StyleChangeRecord styleChangeRecord = (StyleChangeRecord) shapeRecord; + if (styleChangeRecord.stateLineStyle || styleChangeRecord.stateFillStyle0 || styleChangeRecord.stateFillStyle1) { + processSubPath(subPath, currentLineStyleIdx, currentFillStyleIdx0, currentFillStyleIdx1, currentFillEdgeMap, currentLineEdgeMap); + subPath = new ArrayList<>(); + } + if (styleChangeRecord.stateNewStyles) { + fillStyleIdxOffset = fillStyles.size(); + lineStyleIdxOffset = lineStyles.size(); + appendFillStyles(fillStyles, styleChangeRecord.fillStyles.fillStyles); + appendLineStyles(lineStyles, styleChangeRecord.lineStyles.lineStyles); + } + // Check if all styles are reset to 0. + // This (probably) means that a new group starts with the next record + if (styleChangeRecord.stateLineStyle && styleChangeRecord.lineStyle == 0 + && styleChangeRecord.stateFillStyle0 && styleChangeRecord.fillStyle0 == 0 + && styleChangeRecord.stateFillStyle1 && styleChangeRecord.fillStyle1 == 0) { + // do not clean the edges for morphshapes + //cleanEdgeMap(currentFillEdgeMap); + //cleanEdgeMap(currentLineEdgeMap); + fillEdgeMaps.add(currentFillEdgeMap); + lineEdgeMaps.add(currentLineEdgeMap); + currentFillEdgeMap = new HashMap<>(); + currentLineEdgeMap = new HashMap<>(); + currentLineStyleIdx = 0; + currentFillStyleIdx0 = 0; + currentFillStyleIdx1 = 0; + } else { + if (styleChangeRecord.stateLineStyle) { + currentLineStyleIdx = styleChangeRecord.lineStyle; + if (currentLineStyleIdx > 0) { + currentLineStyleIdx += lineStyleIdxOffset; + } + } + if (styleChangeRecord.stateFillStyle0) { + currentFillStyleIdx0 = styleChangeRecord.fillStyle0; + if (currentFillStyleIdx0 > 0) { + currentFillStyleIdx0 += fillStyleIdxOffset; + } + } + if (styleChangeRecord.stateFillStyle1) { + currentFillStyleIdx1 = styleChangeRecord.fillStyle1; + if (currentFillStyleIdx1 > 0) { + currentFillStyleIdx1 += fillStyleIdxOffset; + } + } + } + if (styleChangeRecord.stateMoveTo) { + xPos = styleChangeRecord.moveDeltaX; + yPos = styleChangeRecord.moveDeltaY; + } + + StyleChangeRecord styleChangeRecordEnd = (StyleChangeRecord) shapeRecordEnd; + if (styleChangeRecordEnd.stateMoveTo) { + xPosEnd = styleChangeRecordEnd.moveDeltaX; + yPosEnd = styleChangeRecordEnd.moveDeltaY; + } + } else if (shapeRecord instanceof StraightEdgeRecord) { + StraightEdgeRecord straightEdgeRecord = (StraightEdgeRecord) shapeRecord; + PointInt from = new PointInt(xPos, yPos); + if (straightEdgeRecord.generalLineFlag) { + xPos += straightEdgeRecord.deltaX; + yPos += straightEdgeRecord.deltaY; + } else { + if (straightEdgeRecord.vertLineFlag) { + yPos += straightEdgeRecord.deltaY; + } else { + xPos += straightEdgeRecord.deltaX; + } + } + PointInt to = new PointInt(xPos, yPos); + + StraightEdgeRecord straightEdgeRecordEnd = (StraightEdgeRecord) shapeRecordEnd; + PointInt fromEnd = new PointInt(xPosEnd, yPosEnd); + if (straightEdgeRecordEnd.generalLineFlag) { + xPosEnd += straightEdgeRecordEnd.deltaX; + yPosEnd += straightEdgeRecordEnd.deltaY; + } else { + if (straightEdgeRecordEnd.vertLineFlag) { + yPosEnd += straightEdgeRecordEnd.deltaY; + } else { + xPosEnd += straightEdgeRecordEnd.deltaX; + } + } + PointInt toEnd = new PointInt(xPosEnd, yPosEnd); + + subPath.add(new StraightMorphEdge(from, to, fromEnd, toEnd, currentLineStyleIdx, currentFillStyleIdx1)); + } else if (shapeRecord instanceof CurvedEdgeRecord) { + CurvedEdgeRecord curvedEdgeRecord = (CurvedEdgeRecord) shapeRecord; + PointInt from = new PointInt(xPos, yPos); + int xPosControl = xPos + curvedEdgeRecord.controlDeltaX; + int yPosControl = yPos + curvedEdgeRecord.controlDeltaY; + xPos = xPosControl + curvedEdgeRecord.anchorDeltaX; + yPos = yPosControl + curvedEdgeRecord.anchorDeltaY; + PointInt control = new PointInt(xPosControl, yPosControl); + PointInt to = new PointInt(xPos, yPos); + + CurvedEdgeRecord curvedEdgeRecordEnd = (CurvedEdgeRecord) shapeRecordEnd; + PointInt fromEnd = new PointInt(xPosEnd, yPosEnd); + int xPosControlEnd = xPosEnd + curvedEdgeRecordEnd.controlDeltaX; + int yPosControlEnd = yPosEnd + curvedEdgeRecordEnd.controlDeltaY; + xPosEnd = xPosControlEnd + curvedEdgeRecordEnd.anchorDeltaX; + yPosEnd = yPosControlEnd + curvedEdgeRecordEnd.anchorDeltaY; + PointInt controlEnd = new PointInt(xPosControlEnd, yPosControlEnd); + PointInt toEnd = new PointInt(xPosEnd, yPosEnd); + + subPath.add(new CurvedMorphEdge(from, control, to, fromEnd, controlEnd, toEnd, currentLineStyleIdx, currentFillStyleIdx1)); + } else if (shapeRecord instanceof EndShapeRecord) { + // We're done. Process the last subpath, if any + processSubPath(subPath, currentLineStyleIdx, currentFillStyleIdx0, currentFillStyleIdx1, currentFillEdgeMap, currentLineEdgeMap); + // do not clean the edges for morphshapes + //cleanEdgeMap(currentFillEdgeMap); + //cleanEdgeMap(currentLineEdgeMap); + fillEdgeMaps.add(currentFillEdgeMap); + lineEdgeMaps.add(currentLineEdgeMap); + } + } + edgeMapsCreated = true; + } + } + + protected void processSubPath(List subPath, int lineStyleIdx, int fillStyleIdx0, int fillStyleIdx1, + Map> currentFillEdgeMap, Map> currentLineEdgeMap) { + List path; + if (fillStyleIdx0 != 0) { + path = currentFillEdgeMap.get(fillStyleIdx0); + if (path == null) { + path = new ArrayList<>(); + currentFillEdgeMap.put(fillStyleIdx0, path); + } + for (int j = subPath.size() - 1; j >= 0; j--) { + path.add(subPath.get(j).reverseWithNewFillStyle(fillStyleIdx0)); + } + } + if (fillStyleIdx1 != 0) { + path = currentFillEdgeMap.get(fillStyleIdx1); + if (path == null) { + path = new ArrayList<>(); + currentFillEdgeMap.put(fillStyleIdx1, path); + } + appendEdges(path, subPath); + } + if (lineStyleIdx != 0) { + path = currentLineEdgeMap.get(lineStyleIdx); + if (path == null) { + path = new ArrayList<>(); + currentLineEdgeMap.put(lineStyleIdx, path); + } + appendEdges(path, subPath); + } + } + + protected void exportFillPath(int groupIndex) { + List path = createPathFromEdgeMap(_fillEdgeMaps.get(groupIndex)); + PointInt pos = new PointInt(Integer.MAX_VALUE, Integer.MAX_VALUE); + int fillStyleIdx = Integer.MAX_VALUE; + if (path.size() > 0) { + beginFills(); + for (int i = 0; i < path.size(); i++) { + IMorphEdge e = path.get(i); + if (fillStyleIdx != e.getFillStyleIdx()) { + if (fillStyleIdx != Integer.MAX_VALUE) { + endFill(); + } + fillStyleIdx = e.getFillStyleIdx(); + pos = new PointInt(Integer.MAX_VALUE, Integer.MAX_VALUE); + try { + Matrix matrix; + FILLSTYLE fillStyle = _fillStyles.get(fillStyleIdx - 1); + switch (fillStyle.fillStyleType) { + case FILLSTYLE.SOLID: + // Solid fill + beginFill(colorTransform.apply(fillStyle.color)); + break; + case FILLSTYLE.LINEAR_GRADIENT: + case FILLSTYLE.RADIAL_GRADIENT: + case FILLSTYLE.FOCAL_RADIAL_GRADIENT: + // Gradient fill + matrix = new Matrix(fillStyle.gradientMatrix); + beginGradientFill( + fillStyle.fillStyleType, + colorTransform.apply(fillStyle.gradient.gradientRecords), + matrix, + fillStyle.gradient.spreadMode, + fillStyle.gradient.interpolationMode, + (fillStyle.gradient instanceof FOCALGRADIENT) ? ((FOCALGRADIENT) fillStyle.gradient).focalPoint : 0 + ); + break; + case FILLSTYLE.REPEATING_BITMAP: + case FILLSTYLE.CLIPPED_BITMAP: + case FILLSTYLE.NON_SMOOTHED_REPEATING_BITMAP: + case FILLSTYLE.NON_SMOOTHED_CLIPPED_BITMAP: + // Bitmap fill + matrix = new Matrix(fillStyle.bitmapMatrix); + beginBitmapFill( + fillStyle.bitmapId, + matrix, + (fillStyle.fillStyleType == FILLSTYLE.REPEATING_BITMAP || fillStyle.fillStyleType == FILLSTYLE.NON_SMOOTHED_REPEATING_BITMAP), + (fillStyle.fillStyleType == FILLSTYLE.REPEATING_BITMAP || fillStyle.fillStyleType == FILLSTYLE.CLIPPED_BITMAP), + colorTransform + ); + break; + } + } catch (Exception ex) { + // Font shapes define no fillstyles per se, but do reference fillstyle index 1, + // which represents the font color. We just report null in this case. + beginFill(null); + } + } + if (!pos.equals(e.getFrom())) { + moveTo(e.getFrom().x, e.getFrom().y, e.getFromEnd().x, e.getFromEnd().y); + } + if (e instanceof CurvedMorphEdge) { + CurvedMorphEdge c = (CurvedMorphEdge) e; + curveTo(c.getControl().x, c.getControl().y, c.to.x, c.to.y, c.getControlEnd().x, c.getControlEnd().y, c.toEnd.x, c.toEnd.y); + } else { + lineTo(e.getTo().x, e.getTo().y, e.getToEnd().x, e.getToEnd().y); + } + pos = e.getTo(); + } + if (fillStyleIdx != Integer.MAX_VALUE) { + endFill(); + } + endFills(); + } + } + + protected void exportLinePath(int groupIndex) { + List path = createPathFromEdgeMap(_lineEdgeMaps.get(groupIndex)); + PointInt pos = new PointInt(Integer.MAX_VALUE, Integer.MAX_VALUE); + int lineStyleIdx = Integer.MAX_VALUE; + LINESTYLE lineStyle; + if (path.size() > 0) { + beginLines(); + for (int i = 0; i < path.size(); i++) { + IMorphEdge e = path.get(i); + if (lineStyleIdx != e.getLineStyleIdx()) { + lineStyleIdx = e.getLineStyleIdx(); + pos = new PointInt(Integer.MAX_VALUE, Integer.MAX_VALUE); + try { + lineStyle = _lineStyles.get(lineStyleIdx - 1); + } catch (Exception ex) { + lineStyle = null; + } + if (lineStyle != null) { + String scaleMode = "NORMAL"; + boolean pixelHintingFlag = false; + int startCapStyle = LINESTYLE2.ROUND_CAP; + int endCapStyle = LINESTYLE2.ROUND_CAP; + int joinStyle = LINESTYLE2.ROUND_JOIN; + int miterLimitFactor = 3; + boolean hasFillFlag = false; + if (lineStyle instanceof LINESTYLE2) { + LINESTYLE2 lineStyle2 = (LINESTYLE2) lineStyle; + if (lineStyle2.noHScaleFlag && lineStyle2.noVScaleFlag) { + scaleMode = "NONE"; + } else if (lineStyle2.noHScaleFlag) { + scaleMode = "HORIZONTAL"; + } else if (lineStyle2.noVScaleFlag) { + scaleMode = "VERTICAL"; + } + pixelHintingFlag = lineStyle2.pixelHintingFlag; + startCapStyle = lineStyle2.startCapStyle; + endCapStyle = lineStyle2.endCapStyle; + joinStyle = lineStyle2.joinStyle; + miterLimitFactor = lineStyle2.miterLimitFactor; + hasFillFlag = lineStyle2.hasFillFlag; + } + lineStyle( + lineStyle.width, + colorTransform.apply(lineStyle.color), + pixelHintingFlag, + scaleMode, + startCapStyle, + endCapStyle, + joinStyle, + miterLimitFactor); + + if (hasFillFlag) { + LINESTYLE2 lineStyle2 = (LINESTYLE2) lineStyle; + FILLSTYLE fillStyle = lineStyle2.fillType; + switch (fillStyle.fillStyleType) { + case FILLSTYLE.LINEAR_GRADIENT: + case FILLSTYLE.RADIAL_GRADIENT: + case FILLSTYLE.FOCAL_RADIAL_GRADIENT: + // Gradient fill + Matrix matrix = new Matrix(fillStyle.gradientMatrix); + lineGradientStyle( + fillStyle.fillStyleType, + fillStyle.gradient.gradientRecords, + matrix, + fillStyle.gradient.spreadMode, + fillStyle.gradient.interpolationMode, + (fillStyle.gradient instanceof FOCALGRADIENT) ? ((FOCALGRADIENT) fillStyle.gradient).focalPoint : 0 + ); + break; + } + } + } else { + // We should never get here + lineStyle(1, new RGB(Color.BLACK), false, "NORMAL", 0, 0, 0, 3); + } + } + if (!e.getFrom().equals(pos)) { + moveTo(e.getFrom().x, e.getFrom().y, e.getFromEnd().x, e.getFromEnd().y); + } + if (e instanceof CurvedMorphEdge) { + CurvedMorphEdge c = (CurvedMorphEdge) e; + curveTo(c.getControl().x, c.getControl().y, c.to.x, c.to.y, c.getControlEnd().x, c.getControlEnd().y, c.toEnd.x, c.toEnd.y); + } else { + lineTo(e.getTo().x, e.getTo().y, e.getToEnd().x, e.getToEnd().y); + } + pos = e.getTo(); + } + endLines(); + } + } + + protected List createPathFromEdgeMap(Map> edgeMap) { + List newPath = new ArrayList<>(); + List styleIdxArray = new ArrayList<>(); + for (Integer styleIdx : edgeMap.keySet()) { + styleIdxArray.add(styleIdx); + } + Collections.sort(styleIdxArray); + for (int i = 0; i < styleIdxArray.size(); i++) { + appendEdges(newPath, edgeMap.get(styleIdxArray.get(i))); + } + return newPath; + } + + protected void cleanEdgeMap(Map> edgeMap) { + for (Integer styleIdx : edgeMap.keySet()) { + List subPath = edgeMap.get(styleIdx); + if (subPath != null && subPath.size() > 0) { + int idx; + IMorphEdge prevEdge = null; + List tmpPath = new ArrayList<>(); + Map> coordMap = createCoordMap(subPath); + while (subPath.size() > 0) { + idx = 0; + while (idx < subPath.size()) { + if (prevEdge == null || prevEdge.getTo().equals(subPath.get(idx).getFrom())) { + IMorphEdge edge = subPath.remove(idx); + tmpPath.add(edge); + removeEdgeFromCoordMap(coordMap, edge); + prevEdge = edge; + } else { + IMorphEdge edge = findNextEdgeInCoordMap(coordMap, prevEdge); + if (edge != null) { + idx = subPath.indexOf(edge); + } else { + idx = 0; + prevEdge = null; + } + } + } + } + edgeMap.put(styleIdx, tmpPath); + } + } + } + + protected Map> createCoordMap(List path) { + Map> coordMap = new HashMap<>(); + for (int i = 0; i < path.size(); i++) { + PointInt from = path.get(i).getFrom(); + String key = from.x + "_" + from.y; + List coordMapArray = coordMap.get(key); + if (coordMapArray == null) { + List list = new ArrayList<>(); + list.add(path.get(i)); + coordMap.put(key, list); + } else { + coordMapArray.add(path.get(i)); + } + } + return coordMap; + } + + protected void removeEdgeFromCoordMap(Map> coordMap, IMorphEdge edge) { + String key = edge.getFrom().x + "_" + edge.getFrom().y; + List coordMapArray = coordMap.get(key); + if (coordMapArray != null) { + if (coordMapArray.size() == 1) { + coordMap.remove(key); + } else { + int i = coordMapArray.indexOf(edge); + if (i > -1) { + coordMapArray.remove(i); + } + } + } + } + + protected IMorphEdge findNextEdgeInCoordMap(Map> coordMap, IMorphEdge edge) { + String key = edge.getTo().x + "_" + edge.getTo().y; + List coordMapArray = coordMap.get(key); + if (coordMapArray != null && coordMapArray.size() > 0) { + return coordMapArray.get(0); + } + return null; + } + + protected void appendFillStyles(List v1, FILLSTYLE[] v2) { + v1.addAll(Arrays.asList(v2)); + } + + protected void appendLineStyles(List v1, LINESTYLE[] v2) { + v1.addAll(Arrays.asList(v2)); + } + + protected void appendEdges(List v1, List v2) { + for (int i = 0; i < v2.size(); i++) { + v1.add(v2.get(i)); + } + } +} diff --git a/trunk/src/com/jpexs/decompiler/flash/exporters/PathExporter.java b/trunk/src/com/jpexs/decompiler/flash/exporters/PathExporter.java index 5f586244a..649567ee6 100644 --- a/trunk/src/com/jpexs/decompiler/flash/exporters/PathExporter.java +++ b/trunk/src/com/jpexs/decompiler/flash/exporters/PathExporter.java @@ -56,7 +56,7 @@ public class PathExporter extends ShapeExporterBase { } @Override - public void endShape(double xMin, double yMin, double xMax, double yMax) { + public void endShape() { } diff --git a/trunk/src/com/jpexs/decompiler/flash/exporters/SVGMorphShapeExporter.java b/trunk/src/com/jpexs/decompiler/flash/exporters/SVGMorphShapeExporter.java new file mode 100644 index 000000000..e89759439 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/exporters/SVGMorphShapeExporter.java @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.exporters; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.types.ColorTransform; +import com.jpexs.decompiler.flash.types.FILLSTYLE; +import com.jpexs.decompiler.flash.types.GRADIENT; +import com.jpexs.decompiler.flash.types.GRADRECORD; +import com.jpexs.decompiler.flash.types.LINESTYLE2; +import com.jpexs.decompiler.flash.types.RGB; +import com.jpexs.decompiler.flash.types.RGBA; +import com.jpexs.decompiler.flash.types.SHAPE; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.SerializableImage; +import java.awt.Color; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.imageio.ImageIO; +import javax.xml.bind.DatatypeConverter; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; +import org.w3c.dom.Element; + +/** + * + * @author JPEXS, Claus Wahlers + */ +public class SVGMorphShapeExporter extends DefaultSVGMorphShapeExporter { + + protected static final String sNamespace = "http://www.w3.org/2000/svg"; + protected static final String xlinkNamespace = "http://www.w3.org/1999/xlink"; + + protected Document _svg; + protected Element _svgDefs; + protected Element _svgG; + protected Element path; + protected List gradients; + protected int lastPatternId; + private final SWF swf; + private double maxLineWidth; + private final ExportRectangle bounds; + + public SVGMorphShapeExporter(SWF swf, SHAPE shape, SHAPE endShape, ExportRectangle bounds, ColorTransform colorTransform) { + super(shape, endShape, colorTransform); + this.swf = swf; + this.bounds = bounds; + } + + public String getSVG() { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + StringWriter writer = new StringWriter(); + try { + Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + DOMSource source = new DOMSource(_svg); + StreamResult result = new StreamResult(writer); + transformer.transform(source, result); + } catch (TransformerException ex) { + Logger.getLogger(SVGShapeExporter.class.getName()).log(Level.SEVERE, null, ex); + } + return writer.toString(); + } + + @Override + public void beginShape() { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + try { + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + DOMImplementation impl = docBuilder.getDOMImplementation(); + DocumentType svgDocType = impl.createDocumentType("svg", "-//W3C//DTD SVG 1.0//EN", + "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"); + _svg = impl.createDocument(sNamespace, "svg", svgDocType); + Element svgRoot = _svg.getDocumentElement(); + svgRoot.setAttribute("xmlns:xlink", xlinkNamespace); + _svgDefs = _svg.createElement("defs"); + svgRoot.appendChild(_svgDefs); + _svgG = _svg.createElement("g"); + _svgG.setAttribute("transform", "matrix(1, 0, 0, 1, " + + roundPixels20(-bounds.xMin / (double) SWF.unitDivisor) + ", " + roundPixels20(-bounds.yMin / (double) SWF.unitDivisor) + ")"); + svgRoot.appendChild(_svgG); + } catch (ParserConfigurationException ex) { + Logger.getLogger(SVGShapeExporter.class.getName()).log(Level.SEVERE, null, ex); + } + gradients = new ArrayList<>(); + } + + @Override + public void beginFill(RGB color) { + if (color == null) { + color = new RGB(Color.BLACK); + } + finalizePath(); + path.setAttribute("stroke", "none"); + path.setAttribute("fill", color.toHexRGB()); + if (color instanceof RGBA) { + RGBA colorA = (RGBA) color; + if (colorA.alpha != 255) { + path.setAttribute("fill-opacity", Float.toString(colorA.getAlphaFloat())); + } + } + } + + @Override + public void beginGradientFill(int type, GRADRECORD[] gradientRecords, Matrix matrix, int spreadMethod, int interpolationMethod, float focalPointRatio) { + finalizePath(); + Element gradient = (type == FILLSTYLE.LINEAR_GRADIENT) + ? _svg.createElement("linearGradient") + : _svg.createElement("radialGradient"); + populateGradientElement(gradient, type, gradientRecords, matrix, spreadMethod, interpolationMethod, focalPointRatio); + int id = gradients.indexOf(gradient); + if (id < 0) { + // todo: filter same gradients + id = gradients.size(); + gradients.add(gradient); + } + gradient.setAttribute("id", "gradient" + id); + path.setAttribute("stroke", "none"); + path.setAttribute("fill", "url(#gradient" + id + ")"); + _svgDefs.appendChild(gradient); + } + + @Override + public void beginBitmapFill(int bitmapId, Matrix matrix, boolean repeat, boolean smooth, ColorTransform colorTransform) { + finalizePath(); + ImageTag image = null; + for (Tag t : swf.tags) { + if (t instanceof ImageTag) { + ImageTag i = (ImageTag) t; + if (i.getCharacterId() == bitmapId) { + image = i; + break; + } + } + } + if (image != null) { + SerializableImage img = image.getImage(); + if (img != null) { + colorTransform.apply(img); + int width = img.getWidth(); + int height = img.getHeight(); + lastPatternId++; + String patternId = "PatternID_" + lastPatternId; + String format = image.getImageFormat(); + InputStream imageStream = image.getImageData(); + byte[] imageData; + if (imageStream != null) { + imageData = Helper.readStream(image.getImageData()); + } else { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + ImageIO.write(img.getBufferedImage(), format.toUpperCase(Locale.ENGLISH), baos); + } catch (IOException ex) { + } + imageData = baos.toByteArray(); + } + String base64ImgData = DatatypeConverter.printBase64Binary(imageData); + path.setAttribute("style", "fill:url(#" + patternId + ")"); + Element pattern = _svg.createElement("pattern"); + pattern.setAttribute("id", patternId); + pattern.setAttribute("patternUnits", "userSpaceOnUse"); + pattern.setAttribute("overflow", "visible"); + pattern.setAttribute("width", "" + width); + pattern.setAttribute("height", "" + height); + pattern.setAttribute("viewBox", "0 0 " + width + " " + height); + if (matrix != null) { + double translateX = roundPixels400(matrix.translateX / SWF.unitDivisor); + double translateY = roundPixels400(matrix.translateY / SWF.unitDivisor); + double rotateSkew0 = roundPixels400(matrix.rotateSkew0 / SWF.unitDivisor); + double rotateSkew1 = roundPixels400(matrix.rotateSkew1 / SWF.unitDivisor); + double scaleX = roundPixels400(matrix.scaleX / SWF.unitDivisor); + double scaleY = roundPixels400(matrix.scaleY / SWF.unitDivisor); + pattern.setAttribute("patternTransform", "matrix(" + scaleX + ", " + rotateSkew0 + + ", " + rotateSkew1 + ", " + scaleY + ", " + translateX + ", " + translateY + ")"); + } + Element imageElement = _svg.createElement("image"); + imageElement.setAttribute("width", "" + width); + imageElement.setAttribute("height", "" + height); + imageElement.setAttribute("xlink:href", "data:image/" + format + ";base64," + base64ImgData); + pattern.appendChild(imageElement); + _svgG.appendChild(pattern); + } + } + } + + @Override + public void lineStyle(double thickness, RGB color, boolean pixelHinting, String scaleMode, int startCaps, int endCaps, int joints, int miterLimit) { + finalizePath(); + if (thickness > maxLineWidth) { + maxLineWidth = thickness; + } + thickness /= SWF.unitDivisor; + path.setAttribute("fill", "none"); + path.setAttribute("stroke", color.toHexRGB()); + path.setAttribute("stroke-width", Double.toString(thickness == 0 ? 1 : thickness)); + if (color instanceof RGBA) { + RGBA colorA = (RGBA) color; + if (colorA.alpha != 255) { + path.setAttribute("stroke-opacity", Float.toString(colorA.getAlphaFloat())); + } + } + switch (startCaps) { + case LINESTYLE2.NO_CAP: + path.setAttribute("stroke-linecap", "butt"); + break; + case LINESTYLE2.SQUARE_CAP: + path.setAttribute("stroke-linecap", "square"); + break; + default: + path.setAttribute("stroke-linecap", "round"); + break; + } + switch (joints) { + case LINESTYLE2.BEVEL_JOIN: + path.setAttribute("stroke-linejoin", "bevel"); + break; + case LINESTYLE2.ROUND_JOIN: + path.setAttribute("stroke-linejoin", "round"); + break; + default: + path.setAttribute("stroke-linejoin", "miter"); + if (miterLimit >= 1 && miterLimit != 4) { + path.setAttribute("stroke-miterlimit", Integer.toString(miterLimit)); + } + break; + } + } + + @Override + public void lineGradientStyle(int type, GRADRECORD[] gradientRecords, Matrix matrix, int spreadMethod, int interpolationMethod, float focalPointRatio) { + path.removeAttribute("stroke-opacity"); + Element gradient = (type == FILLSTYLE.LINEAR_GRADIENT) + ? _svg.createElement("linearGradient") + : _svg.createElement("radialGradient"); + populateGradientElement(gradient, type, gradientRecords, matrix, spreadMethod, interpolationMethod, focalPointRatio); + int id = gradients.indexOf(gradient); + if (id < 0) { + // todo: filter same gradients + id = gradients.size(); + gradients.add(gradient); + } + gradient.setAttribute("id", "gradient" + id); + path.setAttribute("stroke", "url(#gradient" + id + ")"); + path.setAttribute("fill", "none"); + _svgDefs.appendChild(gradient); + } + + @Override + protected void finalizePath() { + if (path != null && !"".equals(pathData)) { + path.setAttribute("d", pathData.trim()); + Element animatePath = _svg.createElement("animate"); + animatePath.setAttribute("dur", "2s"); // todo + animatePath.setAttribute("repeatCount", "indefinite"); + animatePath.setAttribute("attributeName", "d"); + animatePath.setAttribute("values", pathData.trim() + ";" + pathDataEnd.trim()); + path.appendChild(animatePath); + _svgG.appendChild(path); + } + path = _svg.createElement("path"); + super.finalizePath(); + } + + protected void populateGradientElement(Element gradient, int type, GRADRECORD[] gradientRecords, Matrix matrix, int spreadMethod, int interpolationMethod, float focalPointRatio) { + gradient.setAttribute("gradientUnits", "userSpaceOnUse"); + if (type == FILLSTYLE.LINEAR_GRADIENT) { + gradient.setAttribute("x1", "-819.2"); + gradient.setAttribute("x2", "819.2"); + } else { + gradient.setAttribute("r", "819.2"); + gradient.setAttribute("cx", "0"); + gradient.setAttribute("cy", "0"); + if (focalPointRatio != 0) { + gradient.setAttribute("fx", Double.toString(819.2 * focalPointRatio)); + gradient.setAttribute("fy", "0"); + } + } + switch (spreadMethod) { + case GRADIENT.SPREAD_PAD_MODE: + gradient.setAttribute("spreadMethod", "pad"); + break; + case GRADIENT.SPREAD_REFLECT_MODE: + gradient.setAttribute("spreadMethod", "reflect"); + break; + case GRADIENT.SPREAD_REPEAT_MODE: + gradient.setAttribute("spreadMethod", "repeat"); + break; + } + if (interpolationMethod == GRADIENT.INTERPOLATION_LINEAR_RGB_MODE) { + gradient.setAttribute("color-interpolation", "linearRGB"); + } + if (matrix != null) { + double translateX = roundPixels400(matrix.translateX / SWF.unitDivisor); + double translateY = roundPixels400(matrix.translateY / SWF.unitDivisor); + double rotateSkew0 = roundPixels400(matrix.rotateSkew0); + double rotateSkew1 = roundPixels400(matrix.rotateSkew1); + double scaleX = roundPixels400(matrix.scaleX); + double scaleY = roundPixels400(matrix.scaleY); + gradient.setAttribute("gradientTransform", "matrix(" + scaleX + ", " + rotateSkew0 + + ", " + rotateSkew1 + ", " + scaleY + ", " + translateX + ", " + translateY + ")"); + } + for (int i = 0; i < gradientRecords.length; i++) { + GRADRECORD record = gradientRecords[i]; + Element gradientEntry = _svg.createElement("stop"); + gradientEntry.setAttribute("offset", Double.toString(record.ratio / 255.0)); + RGB color = record.color; + //if(colors.get(i) != 0) { + gradientEntry.setAttribute("stop-color", color.toHexRGB()); + //} + if (color instanceof RGBA) { + RGBA colorA = (RGBA) color; + if (colorA.alpha != 255) { + gradientEntry.setAttribute("stop-opacity", Float.toString(colorA.getAlphaFloat())); + } + } + gradient.appendChild(gradientEntry); + } + } +} diff --git a/trunk/src/com/jpexs/decompiler/flash/exporters/SVGShapeExporter.java b/trunk/src/com/jpexs/decompiler/flash/exporters/SVGShapeExporter.java index 41d3ae5d2..2c4513ca6 100644 --- a/trunk/src/com/jpexs/decompiler/flash/exporters/SVGShapeExporter.java +++ b/trunk/src/com/jpexs/decompiler/flash/exporters/SVGShapeExporter.java @@ -72,10 +72,12 @@ public class SVGShapeExporter extends DefaultSVGShapeExporter { protected int lastPatternId; private final SWF swf; private double maxLineWidth; + private final ExportRectangle bounds; - public SVGShapeExporter(SWF swf, SHAPE shape, ColorTransform colorTransform) { + public SVGShapeExporter(SWF swf, SHAPE shape, ExportRectangle bounds, ColorTransform colorTransform) { super(shape, colorTransform); this.swf = swf; + this.bounds = bounds; } public String getSVG() { @@ -103,6 +105,8 @@ public class SVGShapeExporter extends DefaultSVGShapeExporter { DocumentType svgDocType = impl.createDocumentType("svg", "-//W3C//DTD SVG 1.0//EN", "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"); _svg = impl.createDocument(sNamespace, "svg", svgDocType); + _svgG.setAttribute("transform", "matrix(1, 0, 0, 1, " + + roundPixels20(-bounds.xMin / (double) SWF.unitDivisor) + ", " + roundPixels20(-bounds.yMin / (double) SWF.unitDivisor) + ")"); Element svgRoot = _svg.getDocumentElement(); svgRoot.setAttribute("xmlns:xlink", xlinkNamespace); _svgDefs = _svg.createElement("defs"); @@ -115,16 +119,6 @@ public class SVGShapeExporter extends DefaultSVGShapeExporter { gradients = new ArrayList<>(); } - @Override - public void endShape(double xMin, double yMin, double xMax, double yMax) { - xMin /= SWF.unitDivisor; - yMin /= SWF.unitDivisor; - xMin -= maxLineWidth / SWF.unitDivisor; - yMin -= maxLineWidth / SWF.unitDivisor; - _svgG.setAttribute("transform", "matrix(1, 0, 0, 1, " - + roundPixels20(-xMin) + ", " + roundPixels20(-yMin) + ")"); - } - @Override public void beginFill(RGB color) { if (color == null) { @@ -287,7 +281,7 @@ public class SVGShapeExporter extends DefaultSVGShapeExporter { @Override protected void finalizePath() { - if (path != null && pathData != "") { + if (path != null && !"".equals(pathData)) { path.setAttribute("d", pathData.trim()); _svgG.appendChild(path); } diff --git a/trunk/src/com/jpexs/decompiler/flash/exporters/ShapeExporterBase.java b/trunk/src/com/jpexs/decompiler/flash/exporters/ShapeExporterBase.java index 21706f7ee..c5d827a07 100644 --- a/trunk/src/com/jpexs/decompiler/flash/exporters/ShapeExporterBase.java +++ b/trunk/src/com/jpexs/decompiler/flash/exporters/ShapeExporterBase.java @@ -48,13 +48,8 @@ public abstract class ShapeExporterBase implements IShapeExporter { protected List _fillStyles; protected List _lineStyles; - protected List>> fillEdgeMaps; - protected List>> lineEdgeMaps; - protected Map> currentFillEdgeMap; - protected Map> currentLineEdgeMap; - private int numGroups; - protected Map> coordMap; - private final ExportRectangle bounds = new ExportRectangle(Double.MAX_VALUE, Double.MAX_VALUE, Double.MIN_VALUE, Double.MIN_VALUE); + protected List>> _fillEdgeMaps; + protected List>> _lineEdgeMaps; private boolean edgeMapsCreated; protected ColorTransform colorTransform; @@ -73,21 +68,25 @@ public abstract class ShapeExporterBase implements IShapeExporter { public void export() { // Create edge maps - createEdgeMaps(); + _fillEdgeMaps = new ArrayList<>(); + _lineEdgeMaps = new ArrayList<>(); + createEdgeMaps(_fillStyles, _lineStyles, _fillEdgeMaps, _lineEdgeMaps); + // Let the doc handler know that a shape export starts beginShape(); // Export fills and strokes for each group separately - for (int i = 0; i < numGroups; i++) { + for (int i = 0; i < _lineEdgeMaps.size(); i++) { // Export fills first exportFillPath(i); // Export strokes last exportLinePath(i); } // Let the doc handler know that we're done exporting a shape - endShape(bounds.xMin, bounds.yMin, bounds.xMax, bounds.yMax); + endShape(); } - protected void createEdgeMaps() { + protected void createEdgeMaps(List fillStyles, List lineStyles, + List>> fillEdgeMaps, List>> lineEdgeMaps) { if (!edgeMapsCreated) { int xPos = 0; int yPos = 0; @@ -97,25 +96,22 @@ public abstract class ShapeExporterBase implements IShapeExporter { int currentFillStyleIdx1 = 0; int currentLineStyleIdx = 0; List subPath = new ArrayList<>(); - numGroups = 0; - fillEdgeMaps = new ArrayList<>(); - lineEdgeMaps = new ArrayList<>(); - currentFillEdgeMap = new HashMap<>(); - currentLineEdgeMap = new HashMap<>(); + Map> currentFillEdgeMap = new HashMap<>(); + Map> currentLineEdgeMap = new HashMap<>(); List records = shape.shapeRecords; for (int i = 0; i < records.size(); i++) { SHAPERECORD shapeRecord = records.get(i); if (shapeRecord instanceof StyleChangeRecord) { StyleChangeRecord styleChangeRecord = (StyleChangeRecord) shapeRecord; if (styleChangeRecord.stateLineStyle || styleChangeRecord.stateFillStyle0 || styleChangeRecord.stateFillStyle1) { - processSubPath(subPath, currentLineStyleIdx, currentFillStyleIdx0, currentFillStyleIdx1); + processSubPath(subPath, currentLineStyleIdx, currentFillStyleIdx0, currentFillStyleIdx1, currentFillEdgeMap, currentLineEdgeMap); subPath = new ArrayList<>(); } if (styleChangeRecord.stateNewStyles) { - fillStyleIdxOffset = _fillStyles.size(); - lineStyleIdxOffset = _lineStyles.size(); - appendFillStyles(_fillStyles, styleChangeRecord.fillStyles.fillStyles); - appendLineStyles(_lineStyles, styleChangeRecord.lineStyles.lineStyles); + fillStyleIdxOffset = fillStyles.size(); + lineStyleIdxOffset = lineStyles.size(); + appendFillStyles(fillStyles, styleChangeRecord.fillStyles.fillStyles); + appendLineStyles(lineStyles, styleChangeRecord.lineStyles.lineStyles); } // Check if all styles are reset to 0. // This (probably) means that a new group starts with the next record @@ -131,7 +127,6 @@ public abstract class ShapeExporterBase implements IShapeExporter { currentLineStyleIdx = 0; currentFillStyleIdx0 = 0; currentFillStyleIdx1 = 0; - numGroups++; } else { if (styleChangeRecord.stateLineStyle) { currentLineStyleIdx = styleChangeRecord.lineStyle; @@ -183,19 +178,19 @@ public abstract class ShapeExporterBase implements IShapeExporter { subPath.add(new CurvedEdge(from, control, to, currentLineStyleIdx, currentFillStyleIdx1)); } else if (shapeRecord instanceof EndShapeRecord) { // We're done. Process the last subpath, if any - processSubPath(subPath, currentLineStyleIdx, currentFillStyleIdx0, currentFillStyleIdx1); + processSubPath(subPath, currentLineStyleIdx, currentFillStyleIdx0, currentFillStyleIdx1, currentFillEdgeMap, currentLineEdgeMap); cleanEdgeMap(currentFillEdgeMap); cleanEdgeMap(currentLineEdgeMap); fillEdgeMaps.add(currentFillEdgeMap); lineEdgeMaps.add(currentLineEdgeMap); - numGroups++; } } edgeMapsCreated = true; } } - protected void processSubPath(List subPath, int lineStyleIdx, int fillStyleIdx0, int fillStyleIdx1) { + protected void processSubPath(List subPath, int lineStyleIdx, int fillStyleIdx0, int fillStyleIdx1, + Map> currentFillEdgeMap, Map> currentLineEdgeMap) { List path; if (fillStyleIdx0 != 0) { path = currentFillEdgeMap.get(fillStyleIdx0); @@ -225,42 +220,14 @@ public abstract class ShapeExporterBase implements IShapeExporter { } } - private void calculateBound(IEdge edge) { - PointInt from = edge.getFrom(); - PointInt to = edge.getTo(); - calculateBound(from); - calculateBound(to); - if (edge instanceof CurvedEdge) { - CurvedEdge curvedEdge = (CurvedEdge) edge; - PointInt control = curvedEdge.getControl(); - calculateBound(control); - } - } - - private void calculateBound(PointInt point) { - if (point.x < bounds.xMin) { - bounds.xMin = point.x; - } - if (point.x > bounds.xMax) { - bounds.xMax = point.x; - } - if (point.y < bounds.yMin) { - bounds.yMin = point.y; - } - if (point.y > bounds.yMax) { - bounds.yMax = point.y; - } - } - protected void exportFillPath(int groupIndex) { - List path = createPathFromEdgeMap(fillEdgeMaps.get(groupIndex)); + List path = createPathFromEdgeMap(_fillEdgeMaps.get(groupIndex)); PointInt pos = new PointInt(Integer.MAX_VALUE, Integer.MAX_VALUE); int fillStyleIdx = Integer.MAX_VALUE; if (path.size() > 0) { beginFills(); for (int i = 0; i < path.size(); i++) { IEdge e = path.get(i); - calculateBound(e); if (fillStyleIdx != e.getFillStyleIdx()) { if (fillStyleIdx != Integer.MAX_VALUE) { endFill(); @@ -329,7 +296,7 @@ public abstract class ShapeExporterBase implements IShapeExporter { } protected void exportLinePath(int groupIndex) { - List path = createPathFromEdgeMap(lineEdgeMaps.get(groupIndex)); + List path = createPathFromEdgeMap(_lineEdgeMaps.get(groupIndex)); PointInt pos = new PointInt(Integer.MAX_VALUE, Integer.MAX_VALUE); int lineStyleIdx = Integer.MAX_VALUE; LINESTYLE lineStyle; @@ -337,7 +304,6 @@ public abstract class ShapeExporterBase implements IShapeExporter { beginLines(); for (int i = 0; i < path.size(); i++) { IEdge e = path.get(i); - calculateBound(e); if (lineStyleIdx != e.getLineStyleIdx()) { lineStyleIdx = e.getLineStyleIdx(); pos = new PointInt(Integer.MAX_VALUE, Integer.MAX_VALUE); @@ -440,17 +406,17 @@ public abstract class ShapeExporterBase implements IShapeExporter { int idx; IEdge prevEdge = null; List tmpPath = new ArrayList<>(); - createCoordMap(subPath); + Map> coordMap = createCoordMap(subPath); while (subPath.size() > 0) { idx = 0; while (idx < subPath.size()) { if (prevEdge == null || prevEdge.getTo().equals(subPath.get(idx).getFrom())) { IEdge edge = subPath.remove(idx); tmpPath.add(edge); - removeEdgeFromCoordMap(edge); + removeEdgeFromCoordMap(coordMap, edge); prevEdge = edge; } else { - IEdge edge = findNextEdgeInCoordMap(prevEdge); + IEdge edge = findNextEdgeInCoordMap(coordMap, prevEdge); if (edge != null) { idx = subPath.indexOf(edge); } else { @@ -465,8 +431,8 @@ public abstract class ShapeExporterBase implements IShapeExporter { } } - protected void createCoordMap(List path) { - coordMap = new HashMap<>(); + protected Map> createCoordMap(List path) { + Map> coordMap = new HashMap<>(); for (int i = 0; i < path.size(); i++) { PointInt from = path.get(i).getFrom(); String key = from.x + "_" + from.y; @@ -479,9 +445,10 @@ public abstract class ShapeExporterBase implements IShapeExporter { coordMapArray.add(path.get(i)); } } + return coordMap; } - protected void removeEdgeFromCoordMap(IEdge edge) { + protected void removeEdgeFromCoordMap(Map> coordMap, IEdge edge) { String key = edge.getFrom().x + "_" + edge.getFrom().y; List coordMapArray = coordMap.get(key); if (coordMapArray != null) { @@ -496,7 +463,7 @@ public abstract class ShapeExporterBase implements IShapeExporter { } } - protected IEdge findNextEdgeInCoordMap(IEdge edge) { + protected IEdge findNextEdgeInCoordMap(Map> coordMap, IEdge edge) { String key = edge.getTo().x + "_" + edge.getTo().y; List coordMapArray = coordMap.get(key); if (coordMapArray != null && coordMapArray.size() > 0) { diff --git a/trunk/src/com/jpexs/decompiler/flash/exporters/StraightMorphEdge.java b/trunk/src/com/jpexs/decompiler/flash/exporters/StraightMorphEdge.java new file mode 100644 index 000000000..f216e95f0 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/exporters/StraightMorphEdge.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2010-2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.exporters; + +/** + * + * @author JPEXS + */ +public class StraightMorphEdge implements IMorphEdge { + + protected final PointInt from; + protected final PointInt to; + protected final PointInt fromEnd; + protected final PointInt toEnd; + protected final int lineStyleIdx; + private final int fillStyleIdx; + + StraightMorphEdge(PointInt from, PointInt to, PointInt fromEnd, PointInt toEnd, int lineStyleIdx, int fillStyleIdx) { + this.from = from; + this.to = to; + this.fromEnd = fromEnd; + this.toEnd = toEnd; + this.lineStyleIdx = lineStyleIdx; + this.fillStyleIdx = fillStyleIdx; + } + + @Override + public PointInt getFrom() { + return from; + } + + @Override + public PointInt getTo() { + return to; + } + + @Override + public PointInt getFromEnd() { + return fromEnd; + } + + @Override + public PointInt getToEnd() { + return toEnd; + } + + @Override + public int getLineStyleIdx() { + return lineStyleIdx; + } + + @Override + public int getFillStyleIdx() { + return fillStyleIdx; + } + + @Override + public IMorphEdge reverseWithNewFillStyle(int newFillStyleIdx) { + return new StraightMorphEdge(to, from, toEnd, fromEnd, lineStyleIdx, newFillStyleIdx); + } +} diff --git a/trunk/src/com/jpexs/decompiler/flash/exporters/modes/MorphshapeExportMode.java b/trunk/src/com/jpexs/decompiler/flash/exporters/modes/MorphshapeExportMode.java index b83bdd378..8ccf9f1ea 100644 --- a/trunk/src/com/jpexs/decompiler/flash/exporters/modes/MorphshapeExportMode.java +++ b/trunk/src/com/jpexs/decompiler/flash/exporters/modes/MorphshapeExportMode.java @@ -22,8 +22,9 @@ package com.jpexs.decompiler.flash.exporters.modes; */ public enum MorphshapeExportMode { - //TODO: implement morphshape export - PNG, - GIF, - AVI + //TODO: implement other morphshape export modes + SVG + //PNG, + //GIF, + //AVI } diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/ExportDialog.java b/trunk/src/com/jpexs/decompiler/flash/gui/ExportDialog.java index 6c19f32e9..287079a79 100644 --- a/trunk/src/com/jpexs/decompiler/flash/gui/ExportDialog.java +++ b/trunk/src/com/jpexs/decompiler/flash/gui/ExportDialog.java @@ -22,6 +22,7 @@ import com.jpexs.decompiler.flash.exporters.modes.BinaryDataExportMode; import com.jpexs.decompiler.flash.exporters.modes.FontExportMode; import com.jpexs.decompiler.flash.exporters.modes.FramesExportMode; import com.jpexs.decompiler.flash.exporters.modes.ImageExportMode; +import com.jpexs.decompiler.flash.exporters.modes.MorphshapeExportMode; import com.jpexs.decompiler.flash.exporters.modes.MovieExportMode; import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; import com.jpexs.decompiler.flash.exporters.modes.ShapeExportMode; @@ -31,6 +32,7 @@ import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag; import com.jpexs.decompiler.flash.tags.base.FontTag; import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; import com.jpexs.decompiler.flash.tags.base.ShapeTag; import com.jpexs.decompiler.flash.tags.base.SoundTag; import com.jpexs.decompiler.flash.tags.base.TextTag; @@ -68,8 +70,8 @@ public class ExportDialog extends AppDialog { "scripts", "binaryData", "frames", - "fonts" - //,"morphshapes" + "fonts", + "morphshapes" }; //Display options only when these classes found @@ -82,8 +84,8 @@ public class ExportDialog extends AppDialog { {TreeNode.class, ScriptPack.class}, {DefineBinaryDataTag.class}, {FrameNodeItem.class}, - {FontTag.class} - //,{MorphShapeTag.class} + {FontTag.class}, + {MorphShapeTag.class} }; //Enum classes for values @@ -96,8 +98,8 @@ public class ExportDialog extends AppDialog { ScriptExportMode.class, BinaryDataExportMode.class, FramesExportMode.class, - FontExportMode.class - //MorphshapeExportMode.class + FontExportMode.class, + MorphshapeExportMode.class }; private final JComboBox[] combos; diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/trunk/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 394d98383..f30d22a4f 100644 --- a/trunk/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/trunk/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -30,6 +30,7 @@ import com.jpexs.decompiler.flash.exporters.modes.BinaryDataExportMode; import com.jpexs.decompiler.flash.exporters.modes.FontExportMode; import com.jpexs.decompiler.flash.exporters.modes.FramesExportMode; import com.jpexs.decompiler.flash.exporters.modes.ImageExportMode; +import com.jpexs.decompiler.flash.exporters.modes.MorphshapeExportMode; import com.jpexs.decompiler.flash.exporters.modes.MovieExportMode; import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; import com.jpexs.decompiler.flash.exporters.modes.ShapeExportMode; @@ -234,7 +235,7 @@ public final class MainPanel extends JPanel implements ActionListener, TreeSelec // play morph shape in 2 second(s) // this settings should be synchronized with frameCount and frameRate - // settings in Mainpanel.createAndShowTempSwf + // settings in MainPanel.createAndShowTempSwf static final int morphShapeAnimationLength = 2; static final int morphShapeAnimationFrameRate = 30; @@ -1138,6 +1139,9 @@ public final class MainPanel extends JPanel implements ActionListener, TreeSelec if (nodeType == TreeNodeType.SHAPE) { ret.add((Tag) n.getItem()); } + if (nodeType == TreeNodeType.MORPH_SHAPE) { + ret.add((Tag) n.getItem()); + } if (nodeType == TreeNodeType.AS) { ret.add(n); } @@ -1183,6 +1187,7 @@ public final class MainPanel extends JPanel implements ActionListener, TreeSelec List as3scripts = new ArrayList<>(); List images = new ArrayList<>(); List shapes = new ArrayList<>(); + List morphshapes = new ArrayList<>(); List movies = new ArrayList<>(); List sounds = new ArrayList<>(); List texts = new ArrayList<>(); @@ -1204,6 +1209,9 @@ public final class MainPanel extends JPanel implements ActionListener, TreeSelec if (nodeType == TreeNodeType.SHAPE) { shapes.add((Tag) n.getItem()); } + if (nodeType == TreeNodeType.MORPH_SHAPE) { + morphshapes.add((Tag) n.getItem()); + } if (nodeType == TreeNodeType.AS) { as12scripts.add(n); } @@ -1258,6 +1266,7 @@ public final class MainPanel extends JPanel implements ActionListener, TreeSelec final ScriptExportMode scriptMode = export.getValue(ScriptExportMode.class); ret.addAll(swf.exportImages(handler, selFile + File.separator + "images", images, export.getValue(ImageExportMode.class))); ret.addAll(SWF.exportShapes(handler, selFile + File.separator + "shapes", shapes, export.getValue(ShapeExportMode.class))); + ret.addAll(SWF.exportMorphShapes(handler, selFile + File.separator + "morphshapes", morphshapes, export.getValue(MorphshapeExportMode.class))); ret.addAll(swf.exportTexts(handler, selFile + File.separator + "texts", texts, export.getValue(TextExportMode.class))); ret.addAll(swf.exportMovies(handler, selFile + File.separator + "movies", movies, export.getValue(MovieExportMode.class))); ret.addAll(swf.exportSounds(handler, selFile + File.separator + "sounds", sounds, export.getValue(SoundExportMode.class))); @@ -1666,6 +1675,7 @@ public final class MainPanel extends JPanel implements ActionListener, TreeSelec } else { swf.exportImages(errorHandler, selFile + File.separator + "images", export.getValue(ImageExportMode.class)); swf.exportShapes(errorHandler, selFile + File.separator + "shapes", export.getValue(ShapeExportMode.class)); + swf.exportMorphShapes(errorHandler, selFile + File.separator + "morphshapes", export.getValue(MorphshapeExportMode.class)); swf.exportTexts(errorHandler, selFile + File.separator + "texts", export.getValue(TextExportMode.class)); swf.exportMovies(errorHandler, selFile + File.separator + "movies", export.getValue(MovieExportMode.class)); swf.exportSounds(errorHandler, selFile + File.separator + "sounds", export.getValue(SoundExportMode.class)); diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog.properties b/trunk/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog.properties index 6bd027f33..6db92cf47 100644 --- a/trunk/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog.properties +++ b/trunk/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog.properties @@ -50,6 +50,7 @@ button.cancel = Cancel morphshapes = Morphshapes morphshapes.gif = GIF +morphshapes.svg = SVG frames = Frames frames.png = PNG diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/DefineMorphShape2Tag.java b/trunk/src/com/jpexs/decompiler/flash/tags/DefineMorphShape2Tag.java index 55d3b7d12..f09b5bb18 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/DefineMorphShape2Tag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/DefineMorphShape2Tag.java @@ -20,8 +20,10 @@ import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; import com.jpexs.decompiler.flash.exporters.BitmapExporter; +import com.jpexs.decompiler.flash.exporters.ExportRectangle; import com.jpexs.decompiler.flash.exporters.Matrix; import com.jpexs.decompiler.flash.exporters.Point; +import com.jpexs.decompiler.flash.exporters.SVGMorphShapeExporter; import com.jpexs.decompiler.flash.tags.base.CharacterTag; import com.jpexs.decompiler.flash.tags.base.DrawableTag; import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; @@ -96,6 +98,16 @@ public class DefineMorphShape2Tag extends CharacterTag implements MorphShapeTag, return rect; } + @Override + public String toSVG() { + ExportRectangle rect = new ExportRectangle(getRect()); + SHAPEWITHSTYLE beginShapes = getShapeAtRatio(0); + SHAPEWITHSTYLE endShapes = getShapeAtRatio(65535); + SVGMorphShapeExporter exporter = new SVGMorphShapeExporter(swf, beginShapes, endShapes, rect, new ColorTransform() /*FIXME?*/); + exporter.export(); + return exporter.getSVG(); + } + @Override public int getCharacterId() { return characterId; diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/DefineMorphShapeTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/DefineMorphShapeTag.java index 139fe2229..3fc3d647b 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/DefineMorphShapeTag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/DefineMorphShapeTag.java @@ -20,8 +20,10 @@ import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; import com.jpexs.decompiler.flash.exporters.BitmapExporter; +import com.jpexs.decompiler.flash.exporters.ExportRectangle; import com.jpexs.decompiler.flash.exporters.Matrix; import com.jpexs.decompiler.flash.exporters.Point; +import com.jpexs.decompiler.flash.exporters.SVGMorphShapeExporter; import com.jpexs.decompiler.flash.tags.base.CharacterTag; import com.jpexs.decompiler.flash.tags.base.DrawableTag; import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; @@ -144,6 +146,16 @@ public class DefineMorphShapeTag extends CharacterTag implements MorphShapeTag, return rect; } + @Override + public String toSVG() { + ExportRectangle rect = new ExportRectangle(getRect()); + SHAPEWITHSTYLE beginShapes = getShapeAtRatio(0); + SHAPEWITHSTYLE endShapes = getShapeAtRatio(65535); + SVGMorphShapeExporter exporter = new SVGMorphShapeExporter(swf, beginShapes, endShapes, rect, new ColorTransform() /*FIXME?*/); + exporter.export(); + return exporter.getSVG(); + } + @Override public RECT getStartBounds() { return startBounds; diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/DefineShape2Tag.java b/trunk/src/com/jpexs/decompiler/flash/tags/DefineShape2Tag.java index 18c8bd946..d502387a9 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/DefineShape2Tag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/DefineShape2Tag.java @@ -18,6 +18,7 @@ package com.jpexs.decompiler.flash.tags; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.exporters.ExportRectangle; import com.jpexs.decompiler.flash.exporters.Point; import com.jpexs.decompiler.flash.exporters.SVGShapeExporter; import com.jpexs.decompiler.flash.tags.base.ShapeTag; @@ -60,7 +61,8 @@ public class DefineShape2Tag extends ShapeTag { @Override public String toSVG() { - SVGShapeExporter exporter = new SVGShapeExporter(swf, getShapes(), new ColorTransform() /*FIXME?*/); + ExportRectangle rect = new ExportRectangle(getRect()); + SVGShapeExporter exporter = new SVGShapeExporter(swf, getShapes(), rect, new ColorTransform() /*FIXME?*/); exporter.export(); return exporter.getSVG(); } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/DefineShape3Tag.java b/trunk/src/com/jpexs/decompiler/flash/tags/DefineShape3Tag.java index 59f9449ee..10c039a29 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/DefineShape3Tag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/DefineShape3Tag.java @@ -18,6 +18,7 @@ package com.jpexs.decompiler.flash.tags; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.exporters.ExportRectangle; import com.jpexs.decompiler.flash.exporters.Point; import com.jpexs.decompiler.flash.exporters.SVGShapeExporter; import com.jpexs.decompiler.flash.tags.base.ShapeTag; @@ -65,7 +66,8 @@ public class DefineShape3Tag extends ShapeTag { @Override public String toSVG() { - SVGShapeExporter exporter = new SVGShapeExporter(swf, getShapes(), new ColorTransform() /*FIXME?*/); + ExportRectangle rect = new ExportRectangle(getRect()); + SVGShapeExporter exporter = new SVGShapeExporter(swf, getShapes(), rect, new ColorTransform() /*FIXME?*/); exporter.export(); return exporter.getSVG(); } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/DefineShape4Tag.java b/trunk/src/com/jpexs/decompiler/flash/tags/DefineShape4Tag.java index 45d9168f9..112c7ce9d 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/DefineShape4Tag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/DefineShape4Tag.java @@ -18,6 +18,7 @@ package com.jpexs.decompiler.flash.tags; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.exporters.ExportRectangle; import com.jpexs.decompiler.flash.exporters.Point; import com.jpexs.decompiler.flash.exporters.SVGShapeExporter; import com.jpexs.decompiler.flash.tags.base.ShapeTag; @@ -68,7 +69,8 @@ public class DefineShape4Tag extends ShapeTag { @Override public String toSVG() { - SVGShapeExporter exporter = new SVGShapeExporter(swf, getShapes(), new ColorTransform() /*FIXME?*/); + ExportRectangle rect = new ExportRectangle(getRect()); + SVGShapeExporter exporter = new SVGShapeExporter(swf, getShapes(), rect, new ColorTransform() /*FIXME?*/); exporter.export(); return exporter.getSVG(); } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/DefineShapeTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/DefineShapeTag.java index 478a3a2b1..09059e7e8 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/DefineShapeTag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/DefineShapeTag.java @@ -19,6 +19,7 @@ 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.ExportRectangle; import com.jpexs.decompiler.flash.exporters.Point; import com.jpexs.decompiler.flash.exporters.SVGShapeExporter; import com.jpexs.decompiler.flash.tags.base.ShapeTag; @@ -93,7 +94,8 @@ public class DefineShapeTag extends ShapeTag { @Override public String toSVG() { - SVGShapeExporter exporter = new SVGShapeExporter(swf, getShapes(), new ColorTransform() /*FIXME?*/); + ExportRectangle rect = new ExportRectangle(getRect()); + SVGShapeExporter exporter = new SVGShapeExporter(swf, getShapes(), rect, new ColorTransform() /*FIXME?*/); exporter.export(); return exporter.getSVG(); } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/base/MorphShapeTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/base/MorphShapeTag.java index 32d5f06de..6264b5510 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/base/MorphShapeTag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/base/MorphShapeTag.java @@ -40,6 +40,8 @@ public interface MorphShapeTag { public SHAPE getEndEdges(); + public abstract String toSVG(); + public int getShapeNum(); public SHAPEWITHSTYLE getShapeAtRatio(int ratio);