From d523b1ba5442b411df5173ddf5649b99830a7718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Wed, 25 Oct 2023 09:33:07 +0200 Subject: [PATCH] Morphashape replacing - correct bitmap handling SVG import - duplicated image on bitmap fill style --- CHANGELOG.md | 1 + .../morphshape/MorphShapeGenerator.java | 30 +++++++++--- .../flash/importers/svg/SvgImporter.java | 23 ++++----- .../flash/importers/svg/SvgStyle.java | 19 +++++-- .../decompiler/flash/tags/base/ImageTag.java | 23 ++++++++- .../decompiler/flash/types/FILLSTYLE.java | 49 ++++++++++++++----- .../decompiler/flash/types/LINESTYLE2.java | 10 ++-- 7 files changed, 115 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 866820fb6..0f7a6dd7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ All notable changes to this project will be documented in this file. - Centered start playing triangle (Playing on demand) - miterLimitFactor is FIXED8 value in MORPHLINESTYLE2 - Display of morphshape end shape to be exactly at 65535 ratio +- SVG import - duplicated image on bitmap fill style ### 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/importers/morphshape/MorphShapeGenerator.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/morphshape/MorphShapeGenerator.java index 0ab99e816..a264c3068 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 @@ -16,6 +16,7 @@ */ package com.jpexs.decompiler.flash.importers.morphshape; +import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFOutputStream; import com.jpexs.decompiler.flash.math.BezierEdge; import com.jpexs.decompiler.flash.tags.DefineMorphShape2Tag; @@ -46,6 +47,9 @@ import java.util.Set; public class MorphShapeGenerator { public void generate(DefineMorphShape2Tag morphShape, ShapeTag startShape, ShapeTag endShape) throws StyleMismatchException { + + SWF swf = morphShape.getSwf(); + ShapeForMorphExporter startExport = new ShapeForMorphExporter(startShape); startExport.export(); ShapeForMorphExporter endExport = new ShapeForMorphExporter(endShape); @@ -86,11 +90,11 @@ public class MorphShapeGenerator { if (((endFillStyle == null && startFillStyle == null) || (startFillStyle != null && endFillStyle != null - && startFillStyle.isCompatibleFillStyle(endFillStyle))) + && startFillStyle.isCompatibleFillStyle(endFillStyle, swf))) && ((endLineStyle == null && startLineStyle == null) || (startLineStyle != null && endLineStyle != null - && startLineStyle.isCompatibleLineStyle(endLineStyle)))) { + && startLineStyle.isCompatibleLineStyle(endLineStyle, swf)))) { double distance = startExport.centralPos.get(a).distance(endExport.centralPos.get(b)); if (distance < minDistance) { minDistance = distance; @@ -135,11 +139,11 @@ public class MorphShapeGenerator { if (((endFillStyle == null && startFillStyle == null) || (startFillStyle != null && endFillStyle != null - && startFillStyle.isCompatibleFillStyle(endFillStyle))) + && startFillStyle.isCompatibleFillStyle(endFillStyle, swf))) && ((endLineStyle == null && startLineStyle == null) || (startLineStyle != null && endLineStyle != null - && startLineStyle.isCompatibleLineStyle(endLineStyle)))) { + && startLineStyle.isCompatibleLineStyle(endLineStyle, swf)))) { double distance = startExport.centralPos.get(a).distance(endExport.centralPos.get(b)); if (distance < minDistance) { minDistance = distance; @@ -199,17 +203,24 @@ public class MorphShapeGenerator { for (int i = 0; i < startFillStyles.size(); i++) { FILLSTYLE fsStart = startFillStyles.get(i); FILLSTYLE fsEnd = endFillStyles.get(i); - MORPHFILLSTYLE morphFillStyle = fsStart.toMorphStyle(fsEnd); + MORPHFILLSTYLE morphFillStyle = fsStart.toMorphStyle(fsEnd, swf); if (morphFillStyle == null) { throw new StyleMismatchException(); } morphFillStyleArray.fillStyles[i] = morphFillStyle; } + + for (int i = 0; i < endFillStyles.size(); i++) { + FILLSTYLE fsEnd = endFillStyles.get(i); + if (fsEnd.hasBitmap()) { + swf.removeTag(swf.getImage(fsEnd.bitmapId)); + } + } for (int i = 0; i < startLineStyles.size(); i++) { LINESTYLE2 lsStart = startLineStyles.get(i); LINESTYLE2 lsEnd = endLineStyles.get(i); - MORPHLINESTYLE2 morphLineStyle = lsStart.toMorphLineStyle2(lsEnd); + MORPHLINESTYLE2 morphLineStyle = lsStart.toMorphLineStyle2(lsEnd, swf); if (morphLineStyle == null) { throw new StyleMismatchException(); } @@ -221,6 +232,13 @@ public class MorphShapeGenerator { morphShape.usesScalingStrokes = true; } } + + for (int i = 0; i < startLineStyles.size(); i++) { + LINESTYLE2 lsEnd = endLineStyles.get(i); + if (lsEnd.hasFillFlag && lsEnd.fillType.hasBitmap()) { + swf.removeTag(swf.getImage(lsEnd.fillType.bitmapId)); + } + } for (int i = 0; i < startBeziers.size(); i++) { List beList = startBeziers.get(i); 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 924761ad4..f7fb16691 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 @@ -217,7 +217,8 @@ public class SvgImporter { this.viewBox = viewBox; - SvgStyle style = new SvgStyle(this, idMap, rootElement); + Map cachedFills = new HashMap<>(); + SvgStyle style = new SvgStyle(this, idMap, rootElement, cachedFills); Matrix transform = new Matrix(); if (fill) { @@ -232,7 +233,7 @@ public class SvgImporter { transform = transform.preConcatenate(Matrix.getScaleInstance(width / viewBox.width, height / viewBox.height)); } - processSvgObject(idMap, shapeNum, shapes, rootElement, transform, style, morphShape); + processSvgObject(idMap, shapeNum, shapes, rootElement, transform, style, morphShape, cachedFills); } catch (SAXException | IOException | ParserConfigurationException ex) { Logger.getLogger(ShapeImporter.class.getName()).log(Level.SEVERE, null, ex); } @@ -318,7 +319,7 @@ public class SvgImporter { } } - private void processSwitch(Element element, Map idMap, int shapeNum, SHAPEWITHSTYLE shapes, Matrix transform, SvgStyle style, boolean morphShape) { + private void processSwitch(Element element, Map idMap, int shapeNum, SHAPEWITHSTYLE shapes, Matrix transform, SvgStyle style, boolean morphShape, Map cachedFills) { for (int i = 0; i < element.getChildNodes().getLength(); i++) { Node childNode = element.getChildNodes().item(i); if (childNode instanceof Element) { @@ -329,31 +330,31 @@ 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); + processElement(childElement, idMap, shapeNum, shapes, transform, style, morphShape, cachedFills); return; } continue; } - processElement(childElement, idMap, shapeNum, shapes, transform, style, morphShape); + processElement(childElement, idMap, shapeNum, shapes, transform, style, morphShape, cachedFills); return; } } } - private void processElement(Element element, Map idMap, int shapeNum, SHAPEWITHSTYLE shapes, Matrix transform, SvgStyle style, boolean morphShape) { + private void processElement(Element element, Map idMap, int shapeNum, SHAPEWITHSTYLE shapes, Matrix transform, SvgStyle style, boolean morphShape, Map cachedFills) { if (element.hasAttribute("requiredExtensions") && !element.getAttribute("requiredExtensions").isEmpty()) { return; } String tagName = element.getTagName(); - SvgStyle newStyle = new SvgStyle(this, idMap, element); + SvgStyle newStyle = new SvgStyle(this, idMap, element, cachedFills); 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); + processSwitch(element, idMap, shapeNum, shapes, transform, style, morphShape, cachedFills); } else if ("style".equals(tagName)) { processStyle(element); } else if ("g".equals(tagName)) { - processSvgObject(idMap, shapeNum, shapes, element, m2, newStyle, morphShape); + processSvgObject(idMap, shapeNum, shapes, element, m2, newStyle, morphShape, cachedFills); } else if ("path".equals(tagName)) { processPath(shapeNum, shapes, element, m2, newStyle, morphShape); } else if ("circle".equals(tagName)) { @@ -378,12 +379,12 @@ public class SvgImporter { } } - private void processSvgObject(Map idMap, int shapeNum, SHAPEWITHSTYLE shapes, Element element, Matrix transform, SvgStyle style, boolean morphShape) { + private void processSvgObject(Map idMap, int shapeNum, SHAPEWITHSTYLE shapes, Element element, Matrix transform, SvgStyle style, boolean morphShape, Map cachedFills) { 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); + processElement(childElement, idMap, shapeNum, shapes, transform, style, morphShape, cachedFills); } } } 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 8e50e0eb8..3a19a5ff9 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 @@ -46,15 +46,18 @@ class SvgStyle { private final SvgImporter importer; private final Map idMap; + + private final Map cachedFills; private final double epsilon = 0.001; private final Random random = new Random(); - public SvgStyle(SvgImporter importer, Map idMap, Element element) { + public SvgStyle(SvgImporter importer, Map idMap, Element element, Map cachedFills) { this.importer = importer; this.idMap = idMap; this.element = element; + this.cachedFills = cachedFills; } private Map getStyleAttributeValues(Element element) { @@ -467,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); + SvgStyle newStyle = new SvgStyle(importer, idMap, stopEl, cachedFills); String offsetStr = stopEl.getAttribute("offset"); double offset = importer.parseNumberOrPercent(offsetStr); @@ -560,15 +563,22 @@ class SvgStyle { if (mPat.matches()) { String elementId = mPat.group(1); + if (cachedFills.containsKey(elementId)) { + return cachedFills.get(elementId); + } Element e = idMap.get(elementId); if (e != null) { String tagName = e.getTagName(); if ("linearGradient".equals(tagName)) { - return parseGradient(idMap, e); + SvgFill ret = parseGradient(idMap, e); + cachedFills.put(elementId, ret); + return ret; } if ("radialGradient".equals(tagName)) { - return parseGradient(idMap, e); + SvgFill ret = parseGradient(idMap, e); + cachedFills.put(elementId, ret); + return ret; } if ("pattern".equals(tagName)) { @@ -599,6 +609,7 @@ class SvgStyle { bitmapFill.patternTransform = e.getAttribute("patternTransform"); } + cachedFills.put(elementId, bitmapFill); 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/tags/base/ImageTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ImageTag.java index 1db45201f..07fd39d8e 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ImageTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ImageTag.java @@ -74,7 +74,7 @@ public abstract class ImageTag extends DrawableTag { public abstract Dimension getImageDimension(); public abstract void setImage(byte[] data) throws IOException; - + public abstract ImageFormat getOriginalImageFormat(); public boolean importSupported() { @@ -315,4 +315,25 @@ public abstract class ImageTag extends DrawableTag { public RECT getRectWithStrokes() { return getRect(); } + + public boolean isSameImage(ImageTag otherImage) { + SerializableImage imgA = getImageCached(); + SerializableImage imgB = otherImage.getImageCached(); + if (imgA.getWidth() != imgB.getWidth() || imgA.getHeight() != imgB.getHeight()) { + return false; + } + + int width = imgA.getWidth(); + int height = imgA.getHeight(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (imgA.getRGB(x, y) != imgB.getRGB(x, y)) { + return false; + } + } + } + + return true; + } } 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 00418939f..6e13bfa19 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 @@ -151,9 +151,30 @@ public class FILLSTYLE implements NeedsCharacters, FieldChangeObserver, Serializ gradient = g; } } - } - - public boolean isCompatibleFillStyle(FILLSTYLE otherFillStyle) { + } + + public boolean hasBitmap() { + switch (fillStyleType) { + case CLIPPED_BITMAP: + case NON_SMOOTHED_CLIPPED_BITMAP: + case NON_SMOOTHED_REPEATING_BITMAP: + case REPEATING_BITMAP: + return true; + } + return false; + } + + public boolean hasGradient() { + switch (fillStyleType) { + case LINEAR_GRADIENT: + case RADIAL_GRADIENT: + case FOCAL_RADIAL_GRADIENT: + return true; + } + return false; + } + + public boolean isCompatibleFillStyle(FILLSTYLE otherFillStyle, SWF swf) { if (fillStyleType != otherFillStyle.fillStyleType) { return false; } @@ -163,7 +184,9 @@ public class FILLSTYLE implements NeedsCharacters, FieldChangeObserver, Serializ case NON_SMOOTHED_REPEATING_BITMAP: case REPEATING_BITMAP: if (bitmapId != otherFillStyle.bitmapId) { - return false; + ImageTag imgThis = swf.getImage(bitmapId); + ImageTag imgOther = swf.getImage(otherFillStyle.bitmapId); + return imgThis.isSameImage(imgOther); } break; case LINEAR_GRADIENT: @@ -176,7 +199,7 @@ public class FILLSTYLE implements NeedsCharacters, FieldChangeObserver, Serializ } return true; } - + public MORPHFILLSTYLE toMorphStyle() { MORPHFILLSTYLE morphFillStyle = new MORPHFILLSTYLE(); morphFillStyle.bitmapId = bitmapId; @@ -191,19 +214,19 @@ public class FILLSTYLE implements NeedsCharacters, FieldChangeObserver, Serializ morphFillStyle.fillStyleType = fillStyleType; if (gradient != null) { morphFillStyle.gradient = gradient.toMorphGradient(); - } - + } + return morphFillStyle; } - - public MORPHFILLSTYLE toMorphStyle(FILLSTYLE endFillStyle) { - if (!isCompatibleFillStyle(endFillStyle)) { + + public MORPHFILLSTYLE toMorphStyle(FILLSTYLE endFillStyle, SWF swf) { + if (!isCompatibleFillStyle(endFillStyle, swf)) { return null; } MORPHFILLSTYLE morphFillStyle = new MORPHFILLSTYLE(); morphFillStyle.bitmapId = bitmapId; if (bitmapMatrix != null) { - morphFillStyle.startBitmapMatrix = new MATRIX(bitmapMatrix); + morphFillStyle.startBitmapMatrix = new MATRIX(bitmapMatrix); } if (endFillStyle.bitmapMatrix != null) { morphFillStyle.endBitmapMatrix = new MATRIX(endFillStyle.bitmapMatrix); @@ -217,8 +240,8 @@ public class FILLSTYLE implements NeedsCharacters, FieldChangeObserver, Serializ morphFillStyle.fillStyleType = fillStyleType; if (gradient != null) { morphFillStyle.gradient = gradient.toMorphGradient(endFillStyle.gradient); - } - + } + return morphFillStyle; } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE2.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE2.java index 1b1a4df57..809fbc551 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE2.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE2.java @@ -143,7 +143,7 @@ public class LINESTYLE2 implements NeedsCharacters, Serializable, ILINESTYLE { this.width = width; } - public boolean isCompatibleLineStyle(LINESTYLE2 otherLineStyle) { + public boolean isCompatibleLineStyle(LINESTYLE2 otherLineStyle, SWF swf) { if (startCapStyle != otherLineStyle.startCapStyle) { return false; } @@ -173,7 +173,7 @@ public class LINESTYLE2 implements NeedsCharacters, Serializable, ILINESTYLE { } if (hasFillFlag) { - if (!fillType.isCompatibleFillStyle(otherLineStyle.fillType)) { + if (!fillType.isCompatibleFillStyle(otherLineStyle.fillType, swf)) { return false; } } @@ -204,8 +204,8 @@ public class LINESTYLE2 implements NeedsCharacters, Serializable, ILINESTYLE { return morphLineStyle2; } - public MORPHLINESTYLE2 toMorphLineStyle2(LINESTYLE2 endLineStyle) { - if (!isCompatibleLineStyle(endLineStyle)) { + public MORPHLINESTYLE2 toMorphLineStyle2(LINESTYLE2 endLineStyle, SWF swf) { + if (!isCompatibleLineStyle(endLineStyle, swf)) { return null; } @@ -228,7 +228,7 @@ public class LINESTYLE2 implements NeedsCharacters, Serializable, ILINESTYLE { morphLineStyle2.endColor = new RGBA(endLineStyle.color); } if (hasFillFlag) { - morphLineStyle2.fillType = fillType.toMorphStyle(endLineStyle.fillType); + morphLineStyle2.fillType = fillType.toMorphStyle(endLineStyle.fillType, swf); } return morphLineStyle2; }