From 87e7db594ebf505aca294b99785285248f7e9573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Fri, 27 Oct 2023 17:13:28 +0200 Subject: [PATCH] Morphshape replace - use whole file when it was exported by FFDec Fixed Morphshape SVG export - focalPoint animation --- CHANGELOG.md | 1 + .../flash/exporters/FrameExporter.java | 2 +- .../flash/exporters/MorphShapeExporter.java | 2 +- .../flash/exporters/ShapeExporter.java | 2 +- .../flash/exporters/TextExporter.java | 2 +- .../flash/exporters/commonshape/Matrix.java | 30 +++ .../exporters/commonshape/SVGExporter.java | 5 +- .../morphshape/SVGMorphShapeExporter.java | 11 +- .../morphshape/MorphShapeGenerator.java | 3 +- .../morphshape/ShapeForMorphExporter.java | 2 +- .../flash/importers/svg/SvgImporter.java | 236 ++++++++++++++---- .../flash/importers/svg/SvgStyle.java | 36 ++- .../decompiler/flash/types/FILLSTYLE.java | 4 + .../decompiler/flash/types/FOCALGRADIENT.java | 4 +- .../decompiler/flash/types/GRADIENT.java | 2 +- .../jpexs/decompiler/flash/gui/MainPanel.java | 55 ++-- 16 files changed, 297 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f66240fec..9430e44b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ All notable changes to this project will be documented in this file. - Display of morphshape end shape to be exactly at 65535 ratio - SVG import - duplicated image on bitmap fill style - Generic tag editor - morphshape fill - show bitmapId for repeating bitmap fill, gradient matrix for focal gradient +- Morphshape SVG export - focalPoint animation ### Changed - Basic tag info panel always visible even when nothing to display (to avoid flickering) diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FrameExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FrameExporter.java index 270e9a34d..62f325ec7 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FrameExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FrameExporter.java @@ -224,7 +224,7 @@ public class FrameExporter { rect.yMax *= settings.zoom; rect.xMin *= settings.zoom; rect.yMin *= settings.zoom; - SVGExporter exporter = new SVGExporter(rect, settings.zoom); + SVGExporter exporter = new SVGExporter(rect, settings.zoom, "frame"); if (fbackgroundColor != null) { exporter.setBackGroundColor(fbackgroundColor); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/MorphShapeExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/MorphShapeExporter.java index 8449d4651..341782c0a 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/MorphShapeExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/MorphShapeExporter.java @@ -102,7 +102,7 @@ public class MorphShapeExporter { rect.yMax *= settings.zoom; rect.xMin *= settings.zoom; rect.yMin *= settings.zoom; - SVGExporter exporter = new SVGExporter(rect, settings.zoom); + SVGExporter exporter = new SVGExporter(rect, settings.zoom, "morphshape"); mst.toSVG(exporter, -2, new CXFORMWITHALPHA(), 0); fos.write(Utf8Helper.getBytes(exporter.getSVG())); } 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 60988ddfe..a2feeca5a 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 @@ -105,7 +105,7 @@ public class ShapeExporter { rect.yMax *= settings.zoom; rect.xMin *= settings.zoom; rect.yMin *= settings.zoom; - SVGExporter exporter = new SVGExporter(rect, settings.zoom); + SVGExporter exporter = new SVGExporter(rect, settings.zoom, "shape"); st.toSVG(exporter, -2, new CXFORMWITHALPHA(), 0); fos.write(Utf8Helper.getBytes(exporter.getSVG())); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/TextExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/TextExporter.java index 2ed0a4ec5..1b089ab90 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/TextExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/TextExporter.java @@ -86,7 +86,7 @@ public class TextExporter { new RetryTask(() -> { try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) { ExportRectangle rect = new ExportRectangle(textTag.getRect()); - SVGExporter exporter = new SVGExporter(rect, settings.zoom); + SVGExporter exporter = new SVGExporter(rect, settings.zoom, "text"); textTag.toSVG(exporter, -2, new CXFORMWITHALPHA(), 0); fos.write(Utf8Helper.getBytes(exporter.getSVG())); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/Matrix.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/Matrix.java index 45c22411e..b0540cde4 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/Matrix.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/Matrix.java @@ -42,6 +42,36 @@ public final class Matrix implements Cloneable { mat.scale(scale); return mat; } + + + public static Matrix getRotateInstance(double rotateAngle) { + return getRotateInstance(rotateAngle, 0, 0); + } + public static Matrix getRotateInstance(double rotateAngle, double tx, double ty) { + double angleRad = -rotateAngle * Math.PI / 180; + Matrix mat = new Matrix(); + mat.rotateSkew0 = -Math.sin(angleRad); + mat.rotateSkew1 = Math.sin(angleRad); + mat.scaleX = Math.cos(angleRad); + mat.scaleY = Math.cos(angleRad); + mat = mat.preConcatenate(getTranslateInstance(tx, ty)) + .concatenate(getTranslateInstance(-tx, -ty)); + return mat; + } + + public static Matrix getSkewXInstance(double skewAngle) { + double angleRad = skewAngle * Math.PI / 180; + Matrix mat = new Matrix(); + mat.rotateSkew1 = Math.tan(angleRad); + return mat; + } + + public static Matrix getSkewYInstance(double skewAngle) { + double angleRad = skewAngle * Math.PI / 180; + Matrix mat = new Matrix(); + mat.rotateSkew0 = Math.tan(angleRad); + return mat; + } public static Matrix getScaleInstance(double scaleX, double scaleY) { Matrix mat = new Matrix(); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/SVGExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/SVGExporter.java index 7ebfa603a..1eeb69ee6 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/SVGExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/SVGExporter.java @@ -88,7 +88,7 @@ public class SVGExporter { public boolean useTextTag = Configuration.textExportExportFontFace.get(); - public SVGExporter(ExportRectangle bounds, double zoom) { + public SVGExporter(ExportRectangle bounds, double zoom, String objectType) { DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); try { @@ -110,11 +110,12 @@ public class SVGExporter { } createDefGroup(bounds, null, zoom); } + svgRoot.setAttribute("ffdec:objectType", objectType); } catch (ParserConfigurationException ex) { Logger.getLogger(SVGExporter.class.getName()).log(Level.SEVERE, null, ex); } gradients = new ArrayList<>(); - } + } private Element getDefs() { if (_svgDefs == null) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/morphshape/SVGMorphShapeExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/morphshape/SVGMorphShapeExporter.java index 5adf81776..00c365749 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/morphshape/SVGMorphShapeExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/morphshape/SVGMorphShapeExporter.java @@ -89,7 +89,7 @@ public class SVGMorphShapeExporter extends DefaultSVGMorphShapeExporter { Element gradient = (type == FILLSTYLE.LINEAR_GRADIENT) ? exporter.createElement("linearGradient") : exporter.createElement("radialGradient"); - populateGradientElement(gradient, type, gradientRecords, gradientRecordsEnd, matrix, matrixEnd, spreadMethod, interpolationMethod, focalPointRatio); + populateGradientElement(gradient, type, gradientRecords, gradientRecordsEnd, matrix, matrixEnd, spreadMethod, interpolationMethod, focalPointRatio, focalPointRatioEnd); int id = exporter.gradients.indexOf(gradient); if (id < 0) { // todo: filter same gradients @@ -206,7 +206,7 @@ public class SVGMorphShapeExporter extends DefaultSVGMorphShapeExporter { Element gradient = (type == FILLSTYLE.LINEAR_GRADIENT) ? exporter.createElement("linearGradient") : exporter.createElement("radialGradient"); - populateGradientElement(gradient, type, gradientRecords, gradientRecordsEnd, matrix, matrixEnd, spreadMethod, interpolationMethod, focalPointRatio); + populateGradientElement(gradient, type, gradientRecords, gradientRecordsEnd, matrix, matrixEnd, spreadMethod, interpolationMethod, focalPointRatio, focalPointRatioEnd); int id = exporter.gradients.indexOf(gradient); if (id < 0) { // todo: filter same gradients @@ -247,7 +247,7 @@ public class SVGMorphShapeExporter extends DefaultSVGMorphShapeExporter { // QR decomposition double translateX = roundPixels400(matrix.translateX * zoom / SWF.unitDivisor); - double translateY = roundPixels400(matrix.translateY * zoom / SWF.unitDivisor); + double translateY = roundPixels400(matrix.translateY * zoom / SWF.unitDivisor); double a = matrix.scaleX; double b = matrix.rotateSkew0; double c = matrix.rotateSkew1; @@ -402,7 +402,7 @@ public class SVGMorphShapeExporter extends DefaultSVGMorphShapeExporter { element.appendChild(animateSkewX);*/ } - protected void populateGradientElement(Element gradient, int type, GRADRECORD[] gradientRecords, GRADRECORD[] gradientRecordsEnd, Matrix matrix, Matrix matrixEnd, int spreadMethod, int interpolationMethod, float focalPointRatio) { + protected void populateGradientElement(Element gradient, int type, GRADRECORD[] gradientRecords, GRADRECORD[] gradientRecordsEnd, Matrix matrix, Matrix matrixEnd, int spreadMethod, int interpolationMethod, float focalPointRatio, float focalPointRatioEnd) { gradient.setAttribute("gradientUnits", "userSpaceOnUse"); if (type == FILLSTYLE.LINEAR_GRADIENT) { gradient.setAttribute("x1", "-819.2"); @@ -415,6 +415,9 @@ public class SVGMorphShapeExporter extends DefaultSVGMorphShapeExporter { gradient.setAttribute("fx", Double.toString(819.2 * focalPointRatio)); gradient.setAttribute("fy", "0"); } + if (focalPointRatio != 0 || focalPointRatioEnd != 0) { + gradient.appendChild(createAnimateElement("fx", Double.toString(819.2 * focalPointRatio), Double.toString(819.2 * focalPointRatioEnd))); + } } switch (spreadMethod) { case GRADIENT.SPREAD_PAD_MODE: diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/morphshape/MorphShapeGenerator.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/morphshape/MorphShapeGenerator.java index a264c3068..a8a67ba8c 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/morphshape/MorphShapeGenerator.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/morphshape/MorphShapeGenerator.java @@ -211,8 +211,9 @@ public class MorphShapeGenerator { } for (int i = 0; i < endFillStyles.size(); i++) { + FILLSTYLE fsStart = startFillStyles.get(i); FILLSTYLE fsEnd = endFillStyles.get(i); - if (fsEnd.hasBitmap()) { + if (fsEnd.hasBitmap() && fsEnd.bitmapId != fsStart.bitmapId) { swf.removeTag(swf.getImage(fsEnd.bitmapId)); } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/morphshape/ShapeForMorphExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/morphshape/ShapeForMorphExporter.java index ee051af45..90d5cc87d 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/morphshape/ShapeForMorphExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/morphshape/ShapeForMorphExporter.java @@ -245,7 +245,7 @@ public class ShapeForMorphExporter extends ShapeExporterBase { currentFillStyle = fillStyles.size(); FILLSTYLE fillStyle = new FILLSTYLE(); fillStyle.fillStyleType = type; - fillStyle.gradient = focalPointRatio == 0 ? new FOCALGRADIENT() : new GRADIENT(); + fillStyle.gradient = focalPointRatio == 0 ? new GRADIENT() : new FOCALGRADIENT(); fillStyle.gradient.gradientRecords = Helper.deepCopy(gradientRecords); fillStyle.gradientMatrix = matrix.toMATRIX(); fillStyle.gradient.spreadMode = spreadMethod; 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 f7fb16691..bb067640a 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 @@ -69,6 +69,7 @@ import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Pattern; import javax.imageio.ImageIO; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -97,28 +98,35 @@ public class SvgImporter { * Shape or morphshape tag */ Tag shapeTag; - + + ShapeTag endShape; + private Rectangle2D.Double viewBox; - + public Tag importSvg(ShapeTag st, String svgXml) { - return importSvg((Tag) st, svgXml, true); + return importSvg((Tag) st, null, svgXml, true); } public Tag importSvg(MorphShapeTag mst, String svgXml) { - return importSvg((Tag) mst, svgXml, true); + return importSvg((Tag) mst, null, svgXml, true); } - + public Tag importSvg(ShapeTag st, String svgXml, boolean fill) { - return importSvg((Tag) st, svgXml, fill); + return importSvg((Tag) st, null, svgXml, fill); } - + public Tag importSvg(MorphShapeTag mst, String svgXml, boolean fill) { - return importSvg((Tag) mst, svgXml, fill); + return importSvg((Tag) mst, null, svgXml, fill); } - - private Tag importSvg(Tag st, String svgXml, boolean fill) { + + public Tag importSvg(ShapeTag startShape, ShapeTag endShape, String svgXml, boolean fill) { + return importSvg((Tag) startShape, endShape, svgXml, fill); + } + + private Tag importSvg(Tag st, ShapeTag endShape, String svgXml, boolean fill) { shapeTag = st; - + this.endShape = endShape; + boolean morphShape = st instanceof MorphShapeTag; if (st instanceof DefineShape4Tag) { @@ -134,10 +142,16 @@ public class SvgImporter { shapes.fillStyles.fillStyles = new FILLSTYLE[0]; shapes.lineStyles.lineStyles = new LINESTYLE[0]; + SHAPEWITHSTYLE shapes2 = new SHAPEWITHSTYLE(); + shapes2.fillStyles = new FILLSTYLEARRAY(); + shapes2.lineStyles = new LINESTYLEARRAY(); + shapes2.fillStyles.fillStyles = new FILLSTYLE[0]; + shapes2.lineStyles.lineStyles = new LINESTYLE[0]; + int shapeNum = 0; RECT rect = null; - - if (st instanceof ShapeTag) { + + if (st instanceof ShapeTag) { shapeNum = ((ShapeTag) st).getShapeNum(); rect = ((ShapeTag) st).getRect(); } @@ -150,11 +164,12 @@ public class SvgImporter { } rect = ((MorphShapeTag) st).getRect(); } - + int origXmin = rect.Xmin; int origYmin = rect.Ymin; shapes.shapeRecords = new ArrayList<>(); + shapes2.shapeRecords = new ArrayList<>(); Rectangle2D.Double viewBox = null; try { @@ -217,8 +232,8 @@ public class SvgImporter { this.viewBox = viewBox; - Map cachedFills = new HashMap<>(); - SvgStyle style = new SvgStyle(this, idMap, rootElement, cachedFills); + Map cachedBitmaps = new HashMap<>(); + SvgStyle style = new SvgStyle(this, idMap, rootElement, cachedBitmaps); Matrix transform = new Matrix(); if (fill) { @@ -227,18 +242,26 @@ public class SvgImporter { transform = Matrix.getScaleInstance(ratioX, ratioY); transform.translate(origXmin / SWF.unitDivisor / ratioX, origYmin / SWF.unitDivisor / ratioY); } - - transform = transform.preConcatenate(Matrix.getTranslateInstance(-viewBox.x, -viewBox.y)); + + transform = transform.preConcatenate(Matrix.getTranslateInstance(-viewBox.x, -viewBox.y)); if (viewBox.height != 0 && viewBox.width != 0) { transform = transform.preConcatenate(Matrix.getScaleInstance(width / viewBox.width, height / viewBox.height)); } - processSvgObject(idMap, shapeNum, shapes, rootElement, transform, style, morphShape, cachedFills); + processSvgObject(idMap, shapeNum, shapes, rootElement, transform, style, morphShape, cachedBitmaps, false); + if ( + rootElement.hasAttribute("ffdec:objectType") + && "morphshape".equals(rootElement.getAttribute("ffdec:objectType")) + && applyAnimation(rootElement) + ) { + processSvgObject(idMap, shapeNum, shapes2, rootElement, transform, style, morphShape, cachedBitmaps, true); + } } catch (SAXException | IOException | ParserConfigurationException ex) { Logger.getLogger(ShapeImporter.class.getName()).log(Level.SEVERE, null, ex); } shapes.shapeRecords.add(new EndShapeRecord()); + shapes2.shapeRecords.add(new EndShapeRecord()); if (st instanceof ShapeTag) { ShapeTag shape = (ShapeTag) st; @@ -247,15 +270,127 @@ public class SvgImporter { shape.updateBounds(); } } + if (endShape != null) { + + endShape.shapes = shapes2; + endShape.updateBounds(); + } if (st instanceof MorphShapeTag) { shapes.updateMorphShapeTag((MorphShapeTag) st, fill); } - + st.setModified(true); + if (endShape != null) { + endShape.setModified(true); + } return (Tag) st; } + protected boolean applyAnimation(Element element) { + NodeList nodeList = element.getChildNodes(); + boolean result = false; + for (int i = 0; i < nodeList.getLength(); i++) { + Node node = nodeList.item(i); + if (node instanceof Element) { + Element childElement = (Element) node; + if ("animate".equals(childElement.getTagName())) { + if (childElement.hasAttribute("attributeName") && childElement.hasAttribute("values")) { + String values = childElement.getAttribute("values"); + String attributeName = childElement.getAttribute("attributeName"); + if (values.contains(";")) { + String parts[] = values.split(";"); + if (parts.length >= 2) { + element.setAttribute(attributeName, parts[1]); + result = true; + } + } + } + } else if ("animateTransform".equals(childElement.getTagName())) { + if (childElement.hasAttribute("attributeName") + && childElement.hasAttribute("type") + && (childElement.hasAttribute("to") || childElement.hasAttribute("values")) + ) { + String type = childElement.getAttribute("type"); + String additive = childElement.hasAttribute("additive") ? childElement.getAttribute("additive") : "replace"; + String attributeName = childElement.getAttribute("attributeName"); + Matrix originalMatrix = Matrix.parseSvgMatrix(element.getAttribute(attributeName), 1, 1); + String to = ""; + if (childElement.hasAttribute("values") && childElement.getAttribute("values").contains(";")) { + to = childElement.getAttribute("values").split(";")[1]; + } else if (childElement.hasAttribute("to")) { + to = childElement.getAttribute("to"); + } + String toParts[] = Matrix.parseSvgNumberList(to); + + Matrix newMatrix = null; + switch (type) { + case "scale": + double scaleX; + double scaleY; + if (toParts.length == 2) { + scaleX = parseNumber(toParts[0]); + scaleY = parseNumber(toParts[1]); + } else if (toParts.length == 1) { + scaleX = parseNumber(toParts[0]); + scaleY = scaleX; + } else { + break; + } + newMatrix = Matrix.getScaleInstance(scaleX, scaleY); + break; + case "translate": + if (toParts.length == 2) { + double translateX = parseNumber(toParts[0]); + double translateY = parseNumber(toParts[1]); + newMatrix = Matrix.getTranslateInstance(translateX, translateY); + } + break; + case "rotate": + if (toParts.length == 1 || toParts.length == 3) { + double rotateAngle = parseNumber(toParts[0]); + double tx = 0; + double ty = 0; + if (toParts.length == 3) { + tx = parseNumber(toParts[1]); + ty = parseNumber(toParts[2]); + } + newMatrix = Matrix.getRotateInstance(rotateAngle, tx, ty); + } + break; + case "skewX": + if (toParts.length == 1) { + double skewXAngle = parseNumber(toParts[0]); + newMatrix = Matrix.getSkewXInstance(skewXAngle); + } + break; + case "skewY": + if (toParts.length == 1) { + double skewYAngle = parseNumber(toParts[0]); + newMatrix = Matrix.getSkewYInstance(skewYAngle); + } + break; + } + if (newMatrix != null) { + if ("replace".equals(additive)) { + element.setAttribute(attributeName, newMatrix.getSvgTransformationString(1, 1)); + } + if ("sum".equals(additive)) { + element.setAttribute(attributeName, originalMatrix.concatenate(newMatrix).getSvgTransformationString(1, 1)); + } + } + } + } else { + if (applyAnimation(childElement)) { + result = true; + } + } + } + + } + return result; + } + // Generate id-element map, because getElementById does not work in some cases (namespaces?) protected void populateIds(Element el, Map out) { if (el.hasAttribute("id")) { @@ -319,7 +454,7 @@ public class SvgImporter { } } - private void processSwitch(Element element, Map idMap, int shapeNum, SHAPEWITHSTYLE shapes, Matrix transform, SvgStyle style, boolean morphShape, Map cachedFills) { + private void processSwitch(Element element, Map idMap, int shapeNum, SHAPEWITHSTYLE shapes, Matrix transform, SvgStyle style, boolean morphShape, Map cachedBitmaps, boolean shape2) { for (int i = 0; i < element.getChildNodes().getLength(); i++) { Node childNode = element.getChildNodes().item(i); if (childNode instanceof Element) { @@ -330,33 +465,33 @@ public class SvgImporter { if (childElement.hasAttribute("systemLanguage")) { String systemLanguage = childElement.getAttribute("systemLanguage"); if (systemLanguage.equals("en-us") || systemLanguage.equals("en")) { - processElement(childElement, idMap, shapeNum, shapes, transform, style, morphShape, cachedFills); + processElement(childElement, idMap, shapeNum, shapes, transform, style, morphShape, cachedBitmaps, shape2); return; } continue; } - processElement(childElement, idMap, shapeNum, shapes, transform, style, morphShape, cachedFills); + processElement(childElement, idMap, shapeNum, shapes, transform, style, morphShape, cachedBitmaps, shape2); return; } } } - private void processElement(Element element, Map idMap, int shapeNum, SHAPEWITHSTYLE shapes, Matrix transform, SvgStyle style, boolean morphShape, Map cachedFills) { + private void processElement(Element element, Map idMap, int shapeNum, SHAPEWITHSTYLE shapes, Matrix transform, SvgStyle style, boolean morphShape, Map cachedBitmaps, boolean shape2) { if (element.hasAttribute("requiredExtensions") && !element.getAttribute("requiredExtensions").isEmpty()) { return; } String tagName = element.getTagName(); - SvgStyle newStyle = new SvgStyle(this, idMap, element, cachedFills); + SvgStyle newStyle = new SvgStyle(this, idMap, element, cachedBitmaps); Matrix m = Matrix.parseSvgMatrix(element.getAttribute("transform"), 1, 1); Matrix m2 = m == null ? transform : transform.concatenate(m); if ("switch".equals(tagName)) { - processSwitch(element, idMap, shapeNum, shapes, transform, style, morphShape, cachedFills); + processSwitch(element, idMap, shapeNum, shapes, transform, style, morphShape, cachedBitmaps, shape2); } else if ("style".equals(tagName)) { processStyle(element); } else if ("g".equals(tagName)) { - processSvgObject(idMap, shapeNum, shapes, element, m2, newStyle, morphShape, cachedFills); + processSvgObject(idMap, shapeNum, shapes, element, m2, newStyle, morphShape, cachedBitmaps, shape2); } else if ("path".equals(tagName)) { - processPath(shapeNum, shapes, element, m2, newStyle, morphShape); + processPath(shapeNum, shapes, element, m2, newStyle, morphShape, shape2); } else if ("circle".equals(tagName)) { processCircle(shapeNum, shapes, element, m2, newStyle, morphShape); } else if ("ellipse".equals(tagName)) { @@ -379,12 +514,12 @@ public class SvgImporter { } } - private void processSvgObject(Map idMap, int shapeNum, SHAPEWITHSTYLE shapes, Element element, Matrix transform, SvgStyle style, boolean morphShape, Map cachedFills) { + private void processSvgObject(Map idMap, int shapeNum, SHAPEWITHSTYLE shapes, Element element, Matrix transform, SvgStyle style, boolean morphShape, Map cachedFills, boolean shape2) { for (int i = 0; i < element.getChildNodes().getLength(); i++) { Node childNode = element.getChildNodes().item(i); if (childNode instanceof Element) { Element childElement = (Element) childNode; - processElement(childElement, idMap, shapeNum, shapes, transform, style, morphShape, cachedFills); + processElement(childElement, idMap, shapeNum, shapes, transform, style, morphShape, cachedFills, shape2); } } } @@ -419,15 +554,19 @@ public class SvgImporter { } } - private void processCommands(int shapeNum, SHAPEWITHSTYLE shapes, List commands, Matrix transform, SvgStyle style, boolean morphShape) { - + private void processCommands(int shapeNum, SHAPEWITHSTYLE shapes, List commands, Matrix transform, SvgStyle style, boolean morphShape, boolean shape2) { + if ("nonzero".equals(style.getFillRule())) { - if (shapeTag instanceof DefineShape4Tag) { + if (!shape2 && (shapeTag instanceof DefineShape4Tag)) { DefineShape4Tag shape4 = (DefineShape4Tag) shapeTag; shape4.usesFillWindingRule = true; } + if (shape2 && (endShape instanceof DefineShape4Tag)) { + DefineShape4Tag shape4 = (DefineShape4Tag) endShape; + shape4.usesFillWindingRule = true; + } } - + Matrix transform2 = transform.preConcatenate(Matrix.getScaleInstance(SWF.unitDivisor)); Point prevPoint = new Point(0, 0); Point startPoint = prevPoint; @@ -484,7 +623,7 @@ public class SvgImporter { scr.fillStyle0 = fillStyle; } else { scr.stateFillStyle1 = true; - scr.fillStyle1 = fillStyle; + scr.fillStyle1 = fillStyle; } } if (lineStyle != 0) { @@ -640,9 +779,7 @@ public class SvgImporter { shapes.shapeRecords.addAll(newRecords); } - private void processPath(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style, boolean morphShape) { - String data = childElement.getAttribute("d"); - + private List processPathD(String data) { char command = 0; Point startPoint = new Point(0, 0); Point prevCControlPoint = null; @@ -927,7 +1064,7 @@ public class SvgImporter { break; default: Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Unknown command: {0}", command); - return; + return new ArrayList<>(); } if (cmd != 'C' && cmd != 'S') { @@ -944,8 +1081,13 @@ public class SvgImporter { } catch (NumberFormatException e) { // ignore remaining data as specified in SVG Specification F.2 Error processing } + return pathCommands; + } - processCommands(shapeNum, shapes, pathCommands, transform, style, morphShape); + private void processPath(int shapeNum, SHAPEWITHSTYLE shapes, Element element, Matrix transform, SvgStyle style, boolean morphShape, boolean shape2) { + String data = element.getAttribute("d"); + List pathCommands = processPathD(data); + processCommands(shapeNum, shapes, pathCommands, transform, style, morphShape, shape2); } private double calcAngle(double ux, double uy, double vx, double vy) { @@ -1043,7 +1185,7 @@ public class SvgImporter { serz.command = 'Z'; pathCommands.add(serz); - processCommands(shapeNum, shapes, pathCommands, transform, style, morphShape); + processCommands(shapeNum, shapes, pathCommands, transform, style, morphShape, false); } private void processRect(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style, boolean morphShape) { @@ -1154,7 +1296,7 @@ public class SvgImporter { serz.command = 'Z'; pathCommands.add(serz); - processCommands(shapeNum, shapes, pathCommands, transform, style, morphShape); + processCommands(shapeNum, shapes, pathCommands, transform, style, morphShape, false); } private void processLine(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style, boolean morphShape) { @@ -1182,8 +1324,8 @@ public class SvgImporter { pathCommands.add(cer); - processCommands(shapeNum, shapes, pathCommands, transform, style, morphShape); - } + processCommands(shapeNum, shapes, pathCommands, transform, style, morphShape, false); + } private void processPolygon(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style, boolean morphShape) { processPolyline(shapeNum, shapes, childElement, transform, style, true, morphShape); @@ -1243,7 +1385,7 @@ public class SvgImporter { pathCommands.add(serz); } - processCommands(shapeNum, shapes, pathCommands, transform, style, morphShape); + processCommands(shapeNum, shapes, pathCommands, transform, style, morphShape, false); } //Stub for w3 test. TODO: refactor and move to test directory. It's here because of easy access - compiling single file @@ -1776,7 +1918,7 @@ public class SvgImporter { SvgLineJoin lineJoin = style.getStrokeLineJoin(); if (lineStyle instanceof LINESTYLE2) { LINESTYLE2 lineStyle2 = (LINESTYLE2) lineStyle; - + String vectorEffect = style.getVectorEffect(); if ("non-scaling-stroke".equals(vectorEffect)) { lineStyle2.noHScaleFlag = true; @@ -1799,7 +1941,7 @@ public class SvgImporter { morph2.usesScalingStrokes = true; } } - + int swfCap = lineCap == SvgLineCap.BUTT ? LINESTYLE2.NO_CAP : lineCap == SvgLineCap.ROUND ? LINESTYLE2.ROUND_CAP : lineCap == SvgLineCap.SQUARE ? LINESTYLE2.SQUARE_CAP : 0; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgStyle.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgStyle.java index 3a19a5ff9..82ee7f6c4 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgStyle.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgStyle.java @@ -47,17 +47,17 @@ class SvgStyle { private final Map idMap; - private final Map cachedFills; + private final Map cachedBitmaps; private final double epsilon = 0.001; private final Random random = new Random(); - public SvgStyle(SvgImporter importer, Map idMap, Element element, Map cachedFills) { + public SvgStyle(SvgImporter importer, Map idMap, Element element, Map cachedBitmaps) { this.importer = importer; this.idMap = idMap; this.element = element; - this.cachedFills = cachedFills; + this.cachedBitmaps = cachedBitmaps; } private Map getStyleAttributeValues(Element element) { @@ -470,7 +470,7 @@ class SvgStyle { Node node = stopNodes.item(i); if (node instanceof Element) { Element stopEl = (Element) node; - SvgStyle newStyle = new SvgStyle(importer, idMap, stopEl, cachedFills); + SvgStyle newStyle = new SvgStyle(importer, idMap, stopEl, cachedBitmaps); String offsetStr = stopEl.getAttribute("offset"); double offset = importer.parseNumberOrPercent(offsetStr); @@ -563,21 +563,26 @@ class SvgStyle { if (mPat.matches()) { String elementId = mPat.group(1); - if (cachedFills.containsKey(elementId)) { - return cachedFills.get(elementId); - } - Element e = idMap.get(elementId); + Element e = idMap.get(elementId); if (e != null) { + if (cachedBitmaps.containsKey(elementId)) { + SvgBitmapFill bitmapFill = new SvgBitmapFill(); + int bitmapId = cachedBitmaps.get(elementId); + bitmapFill.characterId = bitmapId; + if (e.hasAttribute("patternTransform")) { + bitmapFill.patternTransform = e.getAttribute("patternTransform"); + } + return bitmapFill; + } + String tagName = e.getTagName(); if ("linearGradient".equals(tagName)) { SvgFill ret = parseGradient(idMap, e); - cachedFills.put(elementId, ret); return ret; } if ("radialGradient".equals(tagName)) { SvgFill ret = parseGradient(idMap, e); - cachedFills.put(elementId, ret); return ret; } @@ -586,9 +591,14 @@ class SvgStyle { NodeList childNodes = e.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { if (childNodes.item(i) instanceof Element) { - if (element != null) { + + if ("animateTransform".equals(((Element)childNodes.item(i)).getTagName())) { + continue; + } + + if (element != null) { element = null; - break; + break; } element = (Element) childNodes.item(i); @@ -609,7 +619,7 @@ class SvgStyle { bitmapFill.patternTransform = e.getAttribute("patternTransform"); } - cachedFills.put(elementId, bitmapFill); + cachedBitmaps.put(elementId, imageTag.characterID); return bitmapFill; } catch (IOException ex) { Logger.getLogger(SvgStyle.class.getName()).log(Level.SEVERE, null, ex); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/FILLSTYLE.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/FILLSTYLE.java index 6e13bfa19..9aac4f555 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/FILLSTYLE.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/FILLSTYLE.java @@ -213,6 +213,8 @@ public class FILLSTYLE implements NeedsCharacters, FieldChangeObserver, Serializ } morphFillStyle.fillStyleType = fillStyleType; if (gradient != null) { + morphFillStyle.startGradientMatrix = gradientMatrix; + morphFillStyle.endGradientMatrix = gradientMatrix; morphFillStyle.gradient = gradient.toMorphGradient(); } @@ -239,6 +241,8 @@ public class FILLSTYLE implements NeedsCharacters, FieldChangeObserver, Serializ } morphFillStyle.fillStyleType = fillStyleType; if (gradient != null) { + morphFillStyle.startGradientMatrix = gradientMatrix; + morphFillStyle.endGradientMatrix = endFillStyle.gradientMatrix; morphFillStyle.gradient = gradient.toMorphGradient(endFillStyle.gradient); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/FOCALGRADIENT.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/FOCALGRADIENT.java index 69107b668..4c20d7565 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/FOCALGRADIENT.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/FOCALGRADIENT.java @@ -67,8 +67,8 @@ public class FOCALGRADIENT extends GRADIENT implements Serializable { morphGradient.spreadMode = spreadMode; morphGradient.gradientRecords = new MORPHGRADRECORD[gradientRecords.length]; for (int i = 0; i < gradientRecords.length; i++) { - morphGradient.gradientRecords[i] = gradientRecords[i].toMorphGradRecord(); - } + morphGradient.gradientRecords[i] = gradientRecords[i].toMorphGradRecord(endGradient.gradientRecords[i]); + } morphGradient.startFocalPoint = focalPoint; if (endGradient instanceof FOCALGRADIENT) { morphGradient.endFocalPoint = ((FOCALGRADIENT)endGradient).focalPoint; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/GRADIENT.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/GRADIENT.java index c9a566e24..19e8f59ec 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/GRADIENT.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/GRADIENT.java @@ -101,7 +101,7 @@ public class GRADIENT implements Serializable { morphGradient.spreadMode = spreadMode; morphGradient.gradientRecords = new MORPHGRADRECORD[gradientRecords.length]; for (int i = 0; i < gradientRecords.length; i++) { - morphGradient.gradientRecords[i] = gradientRecords[i].toMorphGradRecord(); + morphGradient.gradientRecords[i] = gradientRecords[i].toMorphGradRecord(endGradient.gradientRecords[i]); } if (endGradient instanceof FOCALGRADIENT) { ((MORPHFOCALGRADIENT)morphGradient).startFocalPoint = 0; diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 25e381ce5..e7eab8839 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -4436,11 +4436,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se return false; } - File fileEnd = showImportFileChooser("filter.images|*.jpg;*.jpeg;*.gif;*.png;*.bmp;*.svg", true, AppStrings.translate("dialog.morphshape.endShape")); - - if (fileEnd == null) { - fileEnd = fileStart; - } + DefineShape4Tag shapeStart = new DefineShape4Tag(morphShape.getSwf()); SWF.addTagBefore(shapeStart, morphShape); @@ -4459,36 +4455,45 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } else { dataStart = Helper.readFile(selfileStart.getAbsolutePath()); } - - File selfileEnd = Helper.fixDialogFile(fileEnd); - byte[] dataEnd = null; - String svgTextEnd = null; - if (".svg".equals(Path.getExtension(selfileEnd))) { - svgTextEnd = Helper.readTextFile(selfileEnd.getAbsolutePath()); - if (!svgWarningShown) { - showSvgImportWarning(); - } - } else { - dataEnd = Helper.readFile(selfileEnd.getAbsolutePath()); - } + try { Tag newStartTag; if (svgTextStart != null) { - newStartTag = new SvgImporter().importSvg(shapeStart, svgTextStart, false); + newStartTag = new SvgImporter().importSvg(shapeStart, shapeEnd, svgTextStart, false); } else { newStartTag = new ShapeImporter().importImage(shapeStart, dataStart, 0, false); } newStartTag.getTimelined().removeTag(newStartTag); - Tag newEndTag; - if (svgTextStart != null) { - newEndTag = new SvgImporter().importSvg(shapeEnd, svgTextEnd, false); - } else { - newEndTag = new ShapeImporter().importImage(shapeEnd, dataEnd, 0, false); + if (shapeEnd.shapes.shapeRecords.size() <= 1) { + File fileEnd = showImportFileChooser("filter.images|*.jpg;*.jpeg;*.gif;*.png;*.bmp;*.svg", true, AppStrings.translate("dialog.morphshape.endShape")); + + if (fileEnd == null) { + fileEnd = fileStart; + } + + File selfileEnd = Helper.fixDialogFile(fileEnd); + byte[] dataEnd = null; + String svgTextEnd = null; + if (".svg".equals(Path.getExtension(selfileEnd))) { + svgTextEnd = Helper.readTextFile(selfileEnd.getAbsolutePath()); + if (!svgWarningShown) { + showSvgImportWarning(); + } + } else { + dataEnd = Helper.readFile(selfileEnd.getAbsolutePath()); + } + + Tag newEndTag; + if (svgTextEnd != null) { + newEndTag = new SvgImporter().importSvg(shapeEnd, svgTextEnd, false); + } else { + newEndTag = new ShapeImporter().importImage(shapeEnd, dataEnd, 0, false); + } + newEndTag.getTimelined().removeTag(newEndTag); } - newEndTag.getTimelined().removeTag(newEndTag); - + DefineMorphShape2Tag newMorphShape = new DefineMorphShape2Tag(morphShape.getSwf()); newMorphShape.setTimelined(morphShape.getTimelined()); SWF.addTagBefore(newMorphShape, morphShape);