Morphashape replacing - correct bitmap handling

SVG import - duplicated image on bitmap fill style
This commit is contained in:
Jindra Petřík
2023-11-05 21:11:10 +01:00
parent 048be5cc80
commit d523b1ba54
7 changed files with 115 additions and 40 deletions
@@ -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<BezierEdge> beList = startBeziers.get(i);
@@ -217,7 +217,8 @@ public class SvgImporter {
this.viewBox = viewBox;
SvgStyle style = new SvgStyle(this, idMap, rootElement);
Map<String, SvgFill> 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<String, Element> idMap, int shapeNum, SHAPEWITHSTYLE shapes, Matrix transform, SvgStyle style, boolean morphShape) {
private void processSwitch(Element element, Map<String, Element> idMap, int shapeNum, SHAPEWITHSTYLE shapes, Matrix transform, SvgStyle style, boolean morphShape, Map<String, SvgFill> 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<String, Element> idMap, int shapeNum, SHAPEWITHSTYLE shapes, Matrix transform, SvgStyle style, boolean morphShape) {
private void processElement(Element element, Map<String, Element> idMap, int shapeNum, SHAPEWITHSTYLE shapes, Matrix transform, SvgStyle style, boolean morphShape, Map<String, SvgFill> 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<String, Element> idMap, int shapeNum, SHAPEWITHSTYLE shapes, Element element, Matrix transform, SvgStyle style, boolean morphShape) {
private void processSvgObject(Map<String, Element> idMap, int shapeNum, SHAPEWITHSTYLE shapes, Element element, Matrix transform, SvgStyle style, boolean morphShape, Map<String, SvgFill> 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);
}
}
}
@@ -46,15 +46,18 @@ class SvgStyle {
private final SvgImporter importer;
private final Map<String, Element> idMap;
private final Map<String, SvgFill> cachedFills;
private final double epsilon = 0.001;
private final Random random = new Random();
public SvgStyle(SvgImporter importer, Map<String, Element> idMap, Element element) {
public SvgStyle(SvgImporter importer, Map<String, Element> idMap, Element element, Map<String, SvgFill> cachedFills) {
this.importer = importer;
this.idMap = idMap;
this.element = element;
this.cachedFills = cachedFills;
}
private Map<String, String> 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);
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}