From d2f7f01d32e8a40926b57606e75cc1a0366242c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sun, 29 Sep 2024 23:30:47 +0200 Subject: [PATCH] Added: #2333 Changing Shape tag type (DefineShape, DefineShape2, ...) --- CHANGELOG.md | 4 +- .../flash/tags/DefineShape2Tag.java | 1 + .../flash/tags/DefineShape3Tag.java | 2 +- .../flash/tags/DefineShape4Tag.java | 8 +- .../tags/converters/ShapeTypeConverter.java | 180 ++++++++++++++++++ .../decompiler/flash/types/FILLSTYLE.java | 43 +++++ .../flash/types/FILLSTYLEARRAY.java | 20 ++ .../decompiler/flash/types/FOCALGRADIENT.java | 16 ++ .../decompiler/flash/types/GRADIENT.java | 33 ++++ .../decompiler/flash/types/LINESTYLE.java | 16 ++ .../decompiler/flash/types/LINESTYLE2.java | 44 +++++ .../flash/types/LINESTYLEARRAY.java | 53 ++++++ .../jpexs/decompiler/flash/types/SHAPE.java | 4 +- .../flash/types/SHAPEWITHSTYLE.java | 42 ++++ .../flash/gui/ConvertShapeTypeDialog.java | 130 +++++++++++++ src/com/jpexs/decompiler/flash/gui/Main.java | 3 + .../locales/ConvertShapeTypeDialog.properties | 27 +++ .../ConvertShapeTypeDialog_cs.properties | 27 +++ .../flash/gui/locales/MainFrame.properties | 3 + .../flash/gui/locales/MainFrame_cs.properties | 22 ++- .../flash/gui/tagtree/TagTreeContextMenu.java | 87 ++++++++- 21 files changed, 749 insertions(+), 16 deletions(-) create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/converters/ShapeTypeConverter.java create mode 100644 src/com/jpexs/decompiler/flash/gui/ConvertShapeTypeDialog.java create mode 100644 src/com/jpexs/decompiler/flash/gui/locales/ConvertShapeTypeDialog.properties create mode 100644 src/com/jpexs/decompiler/flash/gui/locales/ConvertShapeTypeDialog_cs.properties diff --git a/CHANGELOG.md b/CHANGELOG.md index 05cda1b1b..3862a128c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file. - Texts spacing is now separated where possible - does not use `[space xx]`, but new `spacing "x" NN` and `spacingpair "x" "y" NN` prefix so now texts are more readable and searchable +- [#2333] Changing Shape tag type (DefineShape, DefineShape2, ...) ### Fixed - [#2319] AS3 Compound assignments problems in some cases @@ -3596,6 +3597,7 @@ Major version of SWF to XML export changed to 2. [#2321]: https://www.free-decompiler.com/flash/issues/2321 [#2305]: https://www.free-decompiler.com/flash/issues/2305 [#2328]: https://www.free-decompiler.com/flash/issues/2328 +[#2333]: https://www.free-decompiler.com/flash/issues/2333 [#2319]: https://www.free-decompiler.com/flash/issues/2319 [#2320]: https://www.free-decompiler.com/flash/issues/2320 [#2272]: https://www.free-decompiler.com/flash/issues/2272 @@ -3606,6 +3608,7 @@ Major version of SWF to XML export changed to 2. [#2329]: https://www.free-decompiler.com/flash/issues/2329 [#2331]: https://www.free-decompiler.com/flash/issues/2331 [#2332]: https://www.free-decompiler.com/flash/issues/2332 +[#2330]: https://www.free-decompiler.com/flash/issues/2330 [#943]: https://www.free-decompiler.com/flash/issues/943 [#1812]: https://www.free-decompiler.com/flash/issues/1812 [#2287]: https://www.free-decompiler.com/flash/issues/2287 @@ -3629,7 +3632,6 @@ Major version of SWF to XML export changed to 2. [#2315]: https://www.free-decompiler.com/flash/issues/2315 [#2316]: https://www.free-decompiler.com/flash/issues/2316 [#2317]: https://www.free-decompiler.com/flash/issues/2317 -[#2330]: https://www.free-decompiler.com/flash/issues/2330 [#2293]: https://www.free-decompiler.com/flash/issues/2293 [#2294]: https://www.free-decompiler.com/flash/issues/2294 [#2299]: https://www.free-decompiler.com/flash/issues/2299 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineShape2Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineShape2Tag.java index cb68904d0..203dd5028 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineShape2Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineShape2Tag.java @@ -28,6 +28,7 @@ import java.io.IOException; /** * DefineShape2 tag - defines shape. Extends functionality of DefineShape. + * Adds more than 255 styles in style list, multiple style lists. * * @author JPEXS */ diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineShape3Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineShape3Tag.java index e8f37f859..a38ead8a7 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineShape3Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineShape3Tag.java @@ -28,7 +28,7 @@ import java.io.IOException; /** * DefineShape3 tag - defines shape. Extends functionality of DefineShape2. - * + * Adds transparency in colors. * @author JPEXS */ @SWFVersion(from = 3) diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineShape4Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineShape4Tag.java index 1a08fab58..87ca8adfc 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineShape4Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineShape4Tag.java @@ -32,7 +32,9 @@ import java.io.IOException; /** * DefineShape4 tag - defines shape. Extends functionality of DefineShape3. - * + * Adds LINESTYLE2 (joins, caps, scaling), filling stroke, edge bounds, + * focal gradient, spread mode, interpolation mode, numgradients > 8. + * * @author JPEXS */ @SWFVersion(from = 8) @@ -115,6 +117,10 @@ public class DefineShape4Tag extends ShapeTag { @Override public void updateBounds() { super.updateBounds(); + updateEdgeBounds(); + } + + public void updateEdgeBounds() { edgeBounds = SHAPERECORD.getBounds(shapes.shapeRecords, null, 4, true); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/converters/ShapeTypeConverter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/converters/ShapeTypeConverter.java new file mode 100644 index 000000000..f1df60290 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/converters/ShapeTypeConverter.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2010-2024 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.tags.converters; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.tags.DefineShape2Tag; +import com.jpexs.decompiler.flash.tags.DefineShape3Tag; +import com.jpexs.decompiler.flash.tags.DefineShape4Tag; +import com.jpexs.decompiler.flash.tags.DefineShapeTag; +import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.ShapeTag; +import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; +import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord; +import com.jpexs.helpers.Helper; + +/** + * Converts shape number (DefineShape, DefineShape2, ...) + * @author JPEXS + */ +public class ShapeTypeConverter { + + /** + * Get minimum DefineShape number, which can be converted to including loosing information. + * @param shapeTag Shape tag + * @return DefineShape number + */ + public int getForcedMinShapeNum(ShapeTag shapeTag) { + if (shapeTag.getShapeNum() > 1) { + if (shapeTag.shapes.fillStyles.fillStyles.length > 255) { + return 2; + } + if (shapeTag.shapes.lineStyles.lineStyles != null && shapeTag.shapes.lineStyles.lineStyles.length > 255) { + return 2; + } + if (shapeTag.shapes.lineStyles.lineStyles2 != null && shapeTag.shapes.lineStyles.lineStyles2.length > 255) { + return 2; + } + for (SHAPERECORD rec : shapeTag.shapes.shapeRecords) { + if (rec instanceof StyleChangeRecord) { + StyleChangeRecord scr = (StyleChangeRecord) rec; + if (scr.stateNewStyles) { + return 2; + } + } + } + } + return 1; + } + + /** + * Get minimum DefineShape number, which can be shape converted to without loosing information. + * @param shapeTag Shape tag + * @return DefineShape number + */ + public int getMinShapeNum(ShapeTag shapeTag) { + int result = shapeTag.shapes.getMinShapeNum(shapeTag.getShapeNum()); + if (shapeTag.getShapeNum() == 4) { + DefineShape4Tag shape4 = (DefineShape4Tag) shapeTag; + if (shape4.usesFillWindingRule) { + return 4; + } + } + return result; + } + + /** + * Converts shape tag referenced by character id in selected SWF file. + * @param swf SWF + * @param characterId Character id + * @param targetShapeNum Target shape num + */ + public void convertCharacter(SWF swf, int characterId, int targetShapeNum) { + CharacterTag ct = swf.getCharacter(characterId); + if (!(ct instanceof ShapeTag)) { + throw new IllegalArgumentException("Character " + characterId + " is not a shape"); + } + ShapeTag sh = (ShapeTag) ct; + if (targetShapeNum == sh.getShapeNum()) { + return; + } + ShapeTag converted = convertTagType(sh, swf, targetShapeNum); + converted.setCharacterId(characterId); + swf.replaceTag(ct, converted); + converted.setTimelined(swf); + swf.updateCharacters(); + swf.clearShapeCache(); + swf.assignClassesToSymbols(); + swf.assignExportNamesToSymbols(); + } + + /** + * Converts DefineShape tag type + * @param sourceShapeTag Source tag + * @param targetSWF Target swf + * @param targetShapeNum Target DefineShape number + * @return Converted DefineShapeX tag + * @throws IllegalArgumentException When conversion is not possible - see getForcedMinShapeNum + */ + public ShapeTag convertTagType(ShapeTag sourceShapeTag, SWF targetSWF, int targetShapeNum) { + int sourceShapeNum = sourceShapeTag.getShapeNum(); + ShapeTag result; + switch (targetShapeNum) { + case 1: + result = new DefineShapeTag(targetSWF); + break; + case 2: + result = new DefineShape2Tag(targetSWF); + break; + case 3: + result = new DefineShape3Tag(targetSWF); + break; + case 4: + result = new DefineShape4Tag(targetSWF); + break; + default: + throw new IllegalArgumentException("Target shape num must be 1-4. Provided: " + targetShapeNum); + } + result.shapeBounds = Helper.deepCopy(sourceShapeTag.shapeBounds); + result.shapes = Helper.deepCopy(sourceShapeTag.shapes); + + if (sourceShapeNum > 1 && targetShapeNum == 1) { + if (result.shapes.fillStyles.fillStyles.length > 255) { + throw new IllegalArgumentException("DefineShape1 does not allow more than 255 fill styles"); + } + if (result.shapes.lineStyles.lineStyles != null && result.shapes.lineStyles.lineStyles.length > 255) { + throw new IllegalArgumentException("DefineShape1 does not allow more than 255 line styles"); + } + if (result.shapes.lineStyles.lineStyles2 != null && result.shapes.lineStyles.lineStyles2.length > 255) { + throw new IllegalArgumentException("DefineShape1 does not allow more than 255 line styles"); + } + for (SHAPERECORD rec : result.shapes.shapeRecords) { + if (rec instanceof StyleChangeRecord) { + StyleChangeRecord scr = (StyleChangeRecord) rec; + if (scr.stateNewStyles) { + throw new IllegalArgumentException("DefineShape1 does not allow multiple style lists"); + } + } + } + } + result.shapes.lineStyles = result.shapes.lineStyles.toShapeNum(sourceShapeNum, targetShapeNum); + result.shapes.fillStyles = result.shapes.fillStyles.toShapeNum(targetShapeNum); + for (SHAPERECORD rec : result.shapes.shapeRecords) { + if (rec instanceof StyleChangeRecord) { + StyleChangeRecord scr = (StyleChangeRecord) rec; + if (scr.stateNewStyles) { + scr.fillStyles = scr.fillStyles.toShapeNum(targetShapeNum); + scr.lineStyles = scr.lineStyles.toShapeNum(sourceShapeNum, targetShapeNum); + } + } + } + if (targetShapeNum == 4) { + DefineShape4Tag result4 = (DefineShape4Tag) result; + if (sourceShapeNum == 4) { + DefineShape4Tag source4 = (DefineShape4Tag) sourceShapeTag; + result4.edgeBounds = Helper.deepCopy(source4.edgeBounds); + result4.usesFillWindingRule = source4.usesFillWindingRule; + result4.usesNonScalingStrokes = source4.usesNonScalingStrokes; + result4.usesScalingStrokes = source4.usesScalingStrokes; + } else { + result4.updateEdgeBounds(); + result4.usesScalingStrokes = true; + } + } + return result; + } +} 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 88c4e9649..3a6d4a53a 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 @@ -28,6 +28,7 @@ import com.jpexs.decompiler.flash.types.annotations.EnumValue; import com.jpexs.decompiler.flash.types.annotations.Internal; import com.jpexs.decompiler.flash.types.annotations.SWFType; import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.helpers.Helper; import java.io.Serializable; import java.lang.reflect.Field; import java.util.Objects; @@ -366,4 +367,46 @@ public class FILLSTYLE implements NeedsCharacters, FieldChangeObserver, Serializ return Objects.equals(this.bitmapMatrix, other.bitmapMatrix); } + public FILLSTYLE toShapeNum(int targetShapeNum) { + FILLSTYLE result = Helper.deepCopy(this); + if (fillStyleType == SOLID) { + if (targetShapeNum < 3) { + result.color = new RGB(color); + } else { + result.color = new RGBA(color); + } + } + if (fillStyleType == LINEAR_GRADIENT + || fillStyleType == RADIAL_GRADIENT + || fillStyleType == FOCAL_RADIAL_GRADIENT + ) { + result.gradient = result.gradient.toShapeNum(targetShapeNum); + } + if (fillStyleType == FOCAL_RADIAL_GRADIENT && targetShapeNum < 4) { + result.fillStyleType = RADIAL_GRADIENT; + } + return result; + } + + public int getMinShapeNum() { + int shapeNum = 1; + if (fillStyleType == SOLID) { + if (color instanceof RGBA) { + RGBA colorA = (RGBA) color; + if (colorA.alpha != 255) { + shapeNum = 3; + } + } + } + if (fillStyleType == LINEAR_GRADIENT || fillStyleType == RADIAL_GRADIENT) { + int gradShapeNum = gradient.getMinShapeNum(); + if (gradShapeNum > shapeNum) { + shapeNum = gradShapeNum; + } + } + if (fillStyleType == FOCAL_RADIAL_GRADIENT) { + shapeNum = 4; + } + return shapeNum; + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/FILLSTYLEARRAY.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/FILLSTYLEARRAY.java index 1d63710d9..0912c1769 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/FILLSTYLEARRAY.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/FILLSTYLEARRAY.java @@ -35,6 +35,26 @@ public class FILLSTYLEARRAY implements NeedsCharacters, Serializable { @SWFArray(value = "fillStyle") public FILLSTYLE[] fillStyles; + public int getMinShapeNum() { + int result = 1; + for (FILLSTYLE fs : fillStyles) { + int sn = fs.getMinShapeNum(); + if (sn > result) { + result = sn; + } + } + return result; + } + + public FILLSTYLEARRAY toShapeNum(int targetShapeNum) { + FILLSTYLEARRAY result = new FILLSTYLEARRAY(); + result.fillStyles = new FILLSTYLE[fillStyles.length]; + for (int i = 0; i < fillStyles.length; i++) { + result.fillStyles[i] = fillStyles[i].toShapeNum(targetShapeNum); + } + return result; + } + @Override public void getNeededCharacters(Set needed, SWF swf) { for (FILLSTYLE fs : fillStyles) { 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 5ac0bf5e4..2bf77869f 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 @@ -17,7 +17,9 @@ package com.jpexs.decompiler.flash.types; import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.helpers.Helper; import java.io.Serializable; +import java.util.Arrays; /** * Focal gradient. Gradient with focal point. Used in radial gradients. @@ -81,4 +83,18 @@ public class FOCALGRADIENT extends GRADIENT implements Serializable { } return morphGradient; } + + public GRADIENT toShapeNum(int shapeNum) { + if (shapeNum < 4) { + GRADIENT result = new GRADIENT(); + result.gradientRecords = Helper.deepCopy(gradientRecords); + result.spreadMode = 0; + result.interpolationMode = 0; + if (result.gradientRecords.length > 8) { + result.gradientRecords = Arrays.copyOfRange(result.gradientRecords, 0, 8); + } + return result; + } + return Helper.deepCopy(this); + } } 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 8920f263a..86e0107ef 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 @@ -19,6 +19,7 @@ package com.jpexs.decompiler.flash.types; import com.jpexs.decompiler.flash.types.annotations.EnumValue; import com.jpexs.decompiler.flash.types.annotations.SWFArray; import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.helpers.Helper; import java.io.Serializable; import java.util.Arrays; @@ -182,5 +183,37 @@ public class GRADIENT implements Serializable { } return Arrays.deepEquals(this.gradientRecords, other.gradientRecords); } + + public GRADIENT toShapeNum(int shapeNum) { + GRADIENT result = Helper.deepCopy(this); + if (shapeNum < 4) { + result.spreadMode = 0; + result.interpolationMode = 0; + if (gradientRecords.length > 8) { + result.gradientRecords = Arrays.copyOfRange(result.gradientRecords, 0, 8); + } + } + return result; + } + public int getMinShapeNum() { + if (gradientRecords.length > 8) { + return 4; + } + if (spreadMode > 0) { + return 4; + } + if (interpolationMode > 0) { + return 4; + } + for (GRADRECORD rec : gradientRecords) { + if (rec.color instanceof RGBA) { + RGBA col = (RGBA) rec.color; + if (col.alpha != 255) { + return 3; + } + } + } + return 1; + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE.java index a38ab9f26..3f177bc36 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE.java @@ -122,5 +122,21 @@ public class LINESTYLE implements NeedsCharacters, Serializable, ILINESTYLE { } return Objects.equals(this.color, other.color); } + + public LINESTYLE2 toLineStyle2() { + LINESTYLE2 result = new LINESTYLE2(); + result.color = new RGBA(color); + result.width = width; + return result; + } + public int getMinShapeNum() { + if (color instanceof RGBA) { + RGBA acolor = (RGBA) color; + if (acolor.alpha != 255) { + return 3; + } + } + return 1; + } } 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 932211dc4..c45c4316d 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 @@ -22,6 +22,7 @@ import com.jpexs.decompiler.flash.types.annotations.Conditional; import com.jpexs.decompiler.flash.types.annotations.EnumValue; import com.jpexs.decompiler.flash.types.annotations.Reserved; import com.jpexs.decompiler.flash.types.annotations.SWFType; +import java.awt.Color; import java.io.Serializable; import java.util.Objects; import java.util.Set; @@ -377,5 +378,48 @@ public class LINESTYLE2 implements NeedsCharacters, Serializable, ILINESTYLE { } return Objects.equals(this.fillType, other.fillType); } + + public LINESTYLE toLineStyle1(int shapeNum) { + LINESTYLE result = new LINESTYLE(); + if (hasFillFlag) { + result.color = shapeNum >= 3 ? new RGBA(Color.black) : new RGB(Color.black); + } else { + result.color = color; + } + result.width = width; + return result; + } + + public int getMinShapeNum() { + int shapeNum = 1; + if (hasFillFlag) { + return 4; + } + if (startCapStyle != ROUND_CAP) { + return 4; + } + if (endCapStyle != ROUND_CAP) { + return 4; + } + if (joinStyle != ROUND_JOIN) { + return 4; + } + if (noClose) { + return 4; + } + if (noHScaleFlag) { + return 4; + } + if (noVScaleFlag) { + return 4; + } + if (pixelHintingFlag) { + return 4; + } + if (color.alpha != 255) { + return 3; + } + return 1; + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLEARRAY.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLEARRAY.java index 28aecd04a..8d23b6917 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLEARRAY.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLEARRAY.java @@ -24,6 +24,7 @@ import com.jpexs.decompiler.flash.tags.DefineShapeTag; import com.jpexs.decompiler.flash.tags.base.NeedsCharacters; import com.jpexs.decompiler.flash.types.annotations.Conditional; import com.jpexs.decompiler.flash.types.annotations.SWFArray; +import com.jpexs.helpers.Helper; import java.io.Serializable; import java.util.Set; @@ -48,6 +49,58 @@ public class LINESTYLEARRAY implements NeedsCharacters, Serializable { @Conditional(tags = {DefineShape4Tag.ID}) public LINESTYLE2[] lineStyles2 = new LINESTYLE2[0]; + public int getMinShapeNum(int sourceShapeNum) { + int result = 1; + if (sourceShapeNum >= 4) { + for (LINESTYLE2 ls : lineStyles2) { + int sn = ls.getMinShapeNum(); + if (sn > result) { + result = sn; + } + } + } else { + for (LINESTYLE ls : lineStyles) { + int sn = ls.getMinShapeNum(); + if (sn > result) { + result = sn; + } + } + } + return result; + } + + public LINESTYLEARRAY toShapeNum(int sourceShapeNum, int targetShapeNum) { + if (sourceShapeNum == targetShapeNum) { + return Helper.deepCopy(this); + } + LINESTYLEARRAY result = new LINESTYLEARRAY(); + if (targetShapeNum >= 4) { + result.lineStyles2 = new LINESTYLE2[lineStyles.length]; + for (int i = 0; i < lineStyles.length; i++) { + result.lineStyles2[i] = lineStyles[i].toLineStyle2(); + } + } else { + result.lineStyles = new LINESTYLE[sourceShapeNum >=4 ? lineStyles2.length : lineStyles.length]; + if (sourceShapeNum >= 4) { + for (int i = 0; i < lineStyles2.length; i++) { + result.lineStyles[i] = lineStyles2[i].toLineStyle1(targetShapeNum); + } + } else { + for (int i = 0; i < lineStyles.length; i++) { + LINESTYLE ls = new LINESTYLE(); + if (targetShapeNum < 3) { + ls.color = new RGB(lineStyles[i].color); + } else { + ls.color = new RGBA(lineStyles[i].color); + } + ls.width = lineStyles[i].width; + result.lineStyles[i] = ls; + } + } + } + return result; + } + @Override public void getNeededCharacters(Set needed, SWF swf) { if (lineStyles != null) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/SHAPE.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/SHAPE.java index efed63296..011a5376b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/SHAPE.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/SHAPE.java @@ -57,8 +57,8 @@ public class SHAPE implements NeedsCharacters, Serializable { @SWFArray(value = "record") public List shapeRecords; - private Shape cachedOutline; - private Shape fastCachedOutline; + private transient Shape cachedOutline; + private transient Shape fastCachedOutline; /** * Constructor. diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/SHAPEWITHSTYLE.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/SHAPEWITHSTYLE.java index 6b3d4a7b2..7b6e3e41f 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/SHAPEWITHSTYLE.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/SHAPEWITHSTYLE.java @@ -249,4 +249,46 @@ public class SHAPEWITHSTYLE extends SHAPE implements NeedsCharacters, Serializab } } + public int getMinShapeNum(int sourceShapeNum) { + int result = 1; + int sn; + + if (fillStyles.fillStyles.length > 255) { + result = 2; + } + if (sourceShapeNum >= 4 && lineStyles.lineStyles2.length > 255) { + result = 2; + } + if (sourceShapeNum < 4 && lineStyles.lineStyles.length > 255) { + result = 2; + } + + sn = fillStyles.getMinShapeNum(); + if (sn > result) { + result = sn; + } + sn = lineStyles.getMinShapeNum(sourceShapeNum); + if (sn > result) { + result = sn; + } + for (SHAPERECORD sr : shapeRecords) { + if (sr instanceof StyleChangeRecord) { + StyleChangeRecord scr = (StyleChangeRecord) sr; + if (scr.stateNewStyles) { + if (2 > result) { + result = 2; + } + sn = scr.fillStyles.getMinShapeNum(); + if (sn > result) { + result = sn; + } + sn = scr.lineStyles.getMinShapeNum(sourceShapeNum); + if (sn > result) { + result = sn; + } + } + } + } + return result; + } } diff --git a/src/com/jpexs/decompiler/flash/gui/ConvertShapeTypeDialog.java b/src/com/jpexs/decompiler/flash/gui/ConvertShapeTypeDialog.java new file mode 100644 index 000000000..118d6fb31 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/ConvertShapeTypeDialog.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2010-2024 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.gui; + +import com.jpexs.decompiler.flash.tags.base.ShapeTag; +import com.jpexs.decompiler.flash.tags.converters.ShapeTypeConverter; +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Container; +import java.awt.FlowLayout; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JRadioButton; + +/** + * + * @author JPEXS + */ +public class ConvertShapeTypeDialog extends AppDialog { + + private List radios = new ArrayList<>(); + + private int result = 0; + + public ConvertShapeTypeDialog(Window owner, int currentShapeNum, int minForced, int min) { + super(owner); + setTitle(translate("dialog.title")); + JPanel radioPanel = new JPanel(); + radioPanel.setLayout(new BoxLayout(radioPanel, BoxLayout.Y_AXIS)); + ButtonGroup radioGroup = new ButtonGroup(); + + JButton okButton = new JButton(translate("button.ok")); + + ShapeTypeConverter converter = new ShapeTypeConverter(); + for (int i = 1; i <= 4; i++) { + String text = "DefineShape" + (i > 1 ? "" + i : ""); + if (i < minForced) { + text += " " + translate("unsupported"); + } + if (i == min) { + text += " " + translate("minimum"); + } + text += " - " + translate("shape" + i); + JRadioButton radio = new JRadioButton(text); + radio.setAlignmentX(Component.LEFT_ALIGNMENT); + if (i < minForced) { + radio.setEnabled(false); + } + if (i == currentShapeNum) { + radio.setSelected(true); + } + final int fi = i; + radio.addActionListener(new ActionListener(){ + @Override + public void actionPerformed(ActionEvent e) { + okButton.setEnabled(fi != currentShapeNum); + } + }); + radioPanel.add(radio); + radioGroup.add(radio); + radios.add(radio); + } + + Container cnt = getContentPane(); + cnt.setLayout(new BorderLayout()); + + cnt.add(radioPanel, BorderLayout.CENTER); + + JPanel buttonsPanel = new JPanel(new FlowLayout()); + + + okButton.setEnabled(false); + okButton.addActionListener(this::okButtonActionPerformed); + JButton cancelButton = new JButton(translate("button.cancel")); + cancelButton.addActionListener(this::cancelButtonActionPerformed); + buttonsPanel.add(okButton); + buttonsPanel.add(cancelButton); + + cnt.add(buttonsPanel, BorderLayout.SOUTH); + pack(); + View.centerScreen(this); + View.setWindowIcon(this, "shape"); + setModal(true); + } + + private void okButtonActionPerformed(ActionEvent evt) { + result = 0; + for (int i = 0; i < radios.size(); i++) { + if (radios.get(i).isSelected()) { + result = i + 1; + break; + } + } + setVisible(false); + } + + private void cancelButtonActionPerformed(ActionEvent evt) { + setVisible(false); + } + + public int getResult() { + return result; + } + + public int showDialog() { + setVisible(true); + return result; + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/Main.java b/src/com/jpexs/decompiler/flash/gui/Main.java index d5e5f3325..7ab5f6f83 100644 --- a/src/com/jpexs/decompiler/flash/gui/Main.java +++ b/src/com/jpexs/decompiler/flash/gui/Main.java @@ -2920,6 +2920,9 @@ public class Main { decodeLaunch5jArgs(args); setSessionLoaded(false); + + System.setProperty("sun.io.serialization.extendedDebugInfo", "true"); + clearTemp(); try { diff --git a/src/com/jpexs/decompiler/flash/gui/locales/ConvertShapeTypeDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/ConvertShapeTypeDialog.properties new file mode 100644 index 000000000..4ba81d7d5 --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/locales/ConvertShapeTypeDialog.properties @@ -0,0 +1,27 @@ +# Copyright (C) 2024 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 . + +dialog.title = Convert shape type + +shape1 = first version +shape2 = more than 255 styles in style list, multiple style lists +shape3 = transparency in colors +shape4 = LINESTYLE2 (joins, caps, scaling, stroke fills), edge bounds, focal gradient, spread mode, interpolation mode, numgradients > 8 + +minimum = (Minimum) +unsupported = (Unsupported) + +button.ok = OK +button.cancel = Cancel \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/ConvertShapeTypeDialog_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/ConvertShapeTypeDialog_cs.properties new file mode 100644 index 000000000..e2440c3cf --- /dev/null +++ b/src/com/jpexs/decompiler/flash/gui/locales/ConvertShapeTypeDialog_cs.properties @@ -0,0 +1,27 @@ +# Copyright (C) 2024 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 . + +dialog.title = Konvertovat typ tvaru + +shape1 = prvn\u00ed verze +shape2 = v\u00edce ne\u017e 255 styl\u016f v seznamu styl\u016f, vice seznam\u016f styl\u016f +shape3 = pr\u016fhlednost v barv\u00e1ch +shape4 = LINESTYLE2 (joins, caps, scaling, v\u00fdpl\u0148 tah\u016f), edge bounds, focal gradient, spread mode, interpolation mode, po\u010det gradient\u016f > 8 + +minimum = (Minimum) +unsupported = (Nepodporov\u00e1no) + +button.ok = OK +button.cancel = Storno \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 45dd48d56..971edfa7b 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1002,3 +1002,6 @@ packer.key = Enter key for the packer "%packer%": contextmenu.exportVsCode = Export to VS Code work.exporting.vsCode = Exporting VS Code menu.file.export.vsCode = Export to VS Code + +#after 21.1.0 +contextmenu.convertShapeType = Convert shape type \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties index aa9fa8bcd..4a0656455 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties @@ -954,14 +954,14 @@ warning.cleanAbc = Tato akce odstran\u00ed z ABC polo\u017eky, kter\u00e9 maj\u0 tagInfo.idType = Typ id contextmenu.configurePathResolving = Nastavit resolvov\u00e1n\u00ed cest... contextmenu.setAsLinkage = Nastavit AS vazbu -contextmenu.exportFlashDevelop = Exportovat FlashDevelop projekt +contextmenu.exportFlashDevelop = Exportovat do FlashDevelop filter.as3proj=FlashDevelop AS3 projekty (*.as3proj) work.exporting.flashDevelop = Exportov\u00e1n\u00ed FlashDevelop projektu -menu.file.export.flashDevelop = Exportovat FD projekt +menu.file.export.flashDevelop = Exportovat do FlashDevelop contextmenu.exportIdea = Exportovat IDEA project filter.iml = IntelliJ IDEA projekty (*.iml) work.exporting.idea = Exportov\u00e1n\u00ed IntelliJ IDEA projektu -menu.file.export.idea = Exportovat IDEA projekt +menu.file.export.idea = Exportovat do IntelliJ IDEA contextmenu.exportFla = Exportovat do FLA export.project.select.directory = Vyberte um\u00edst\u011bn\u00ed nov\u00e9ho adres\u00e1\u0159e projektu callStack.header.swf = SWF @@ -972,4 +972,18 @@ message.confirm.addfunction = Tato akce vytvo\u0159\u00ed nov\u00fd objekt Metho kter\u00fd m\u016f\u017eete pou\u017e\u00edt jako operand pro instrukci newfunction.\r\n\ Pro editaci t\u011bla a parametr\u016f takov\u00e9 funkce ji mus\u00edte pot\u00e9 vyhledat v ActionScript pohledu. addfunction.result = Nov\u00e9 id method infa: %method_info_index%. Stiskn\u011bte OK pro zkop\u00edrov\u00e1n\u00ed do schr\u00e1nky. -addfunction.result.title = Nov\u00e9 method info \ No newline at end of file +addfunction.result.title = Nov\u00e9 method info + +#after 21.0.2 +filter.air.as3proj = FlashDevelop AIR AS3 projekt (*.as3proj) + +packer.key.title = Kl\u00ed\u010d pro: %packer% +packer.key = Zadejte kl\u00ed\u010d pro packer "%packer%": + +#after 21.0.5 +contextmenu.exportVsCode = Exportovat do VS Code +work.exporting.vsCode = Exportuji VS Code +menu.file.export.vsCode = Exportovat do VS Code + +#after 21.1.0 +contextmenu.convertShapeType = Konvertovat typ tvaru \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java index d25308994..30e3a96c7 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -42,6 +42,7 @@ import com.jpexs.decompiler.flash.gui.AppStrings; import com.jpexs.decompiler.flash.gui.AsLinkageDialog; import com.jpexs.decompiler.flash.gui.ClipboardType; import com.jpexs.decompiler.flash.gui.CollectDepthAsSpritesDialog; +import com.jpexs.decompiler.flash.gui.ConvertShapeTypeDialog; import com.jpexs.decompiler.flash.gui.Main; import com.jpexs.decompiler.flash.gui.MainPanel; import com.jpexs.decompiler.flash.gui.PathResolvingDialog; @@ -103,6 +104,7 @@ import com.jpexs.decompiler.flash.tags.base.RemoveTag; import com.jpexs.decompiler.flash.tags.base.ShapeTag; import com.jpexs.decompiler.flash.tags.base.SoundTag; import com.jpexs.decompiler.flash.tags.base.TextTag; +import com.jpexs.decompiler.flash.tags.converters.ShapeTypeConverter; import com.jpexs.decompiler.flash.tags.gfx.ExporterInfo; import com.jpexs.decompiler.flash.timeline.AS2Package; import com.jpexs.decompiler.flash.timeline.AS3Package; @@ -335,7 +337,9 @@ public class TagTreeContextMenu extends JPopupMenu { private JMenuItem replaceWithGifMenuItem; - private JMenuItem collectDepthAsSpritesItem; + private JMenuItem collectDepthAsSpritesMenuItem; + + private JMenuItem convertShapeTypeMenuItem; private List items = new ArrayList<>(); @@ -534,6 +538,11 @@ public class TagTreeContextMenu extends JPopupMenu { replaceRefsWithTagMenuItem.addActionListener(this::replaceRefsWithTagActionPerformed); replaceRefsWithTagMenuItem.setIcon(View.getIcon("replacewithtag16")); add(replaceRefsWithTagMenuItem); + + convertShapeTypeMenuItem = new JMenuItem(mainPanel.translate("contextmenu.convertShapeType")); + convertShapeTypeMenuItem.addActionListener(this::convertShapeTypeActionPerformed); + convertShapeTypeMenuItem.setIcon(View.getIcon("shape16")); + add(convertShapeTypeMenuItem); addSeparator(); @@ -761,10 +770,10 @@ public class TagTreeContextMenu extends JPopupMenu { pasteInsideMenuItem.addActionListener(this::pasteInsideActionPerformed); add(pasteInsideMenuItem); - collectDepthAsSpritesItem = new JMenuItem(mainPanel.translate("contextmenu.collectDepthAsSprites")); - collectDepthAsSpritesItem.setIcon(View.getIcon("sprite16")); - collectDepthAsSpritesItem.addActionListener(this::collectDepthAsSprites); - add(collectDepthAsSpritesItem); + collectDepthAsSpritesMenuItem = new JMenuItem(mainPanel.translate("contextmenu.collectDepthAsSprites")); + collectDepthAsSpritesMenuItem.setIcon(View.getIcon("sprite16")); + collectDepthAsSpritesMenuItem.addActionListener(this::collectDepthAsSprites); + add(collectDepthAsSpritesMenuItem); addSeparator(); @@ -1064,7 +1073,19 @@ public class TagTreeContextMenu extends JPopupMenu { } tim = fr.timeline.timelined; } + + boolean allSelectedIsShape = true; + + if (items.isEmpty()) { + allSelectedIsShape = false; + } + for (TreeItem item : items) { + + if (!(item instanceof ShapeTag)) { + allSelectedIsShape = false; + } + if (item instanceof Tag) { Tag tag = (Tag) item; if (tag.isReadOnly()) { @@ -1214,6 +1235,7 @@ public class TagTreeContextMenu extends JPopupMenu { replaceWithGifMenuItem.setVisible(false); replaceWithTagMenuItem.setVisible(false); replaceRefsWithTagMenuItem.setVisible(false); + convertShapeTypeMenuItem.setVisible(false); abcExplorerMenuItem.setVisible(false); cleanAbcMenuItem.setVisible(false); rawEditMenuItem.setVisible(false); @@ -1256,7 +1278,7 @@ public class TagTreeContextMenu extends JPopupMenu { pasteAfterMenuItem.setVisible(false); pasteBeforeMenuItem.setVisible(false); pasteInsideMenuItem.setVisible(false); - collectDepthAsSpritesItem.setVisible(allSelectedIsFrame && allSelectedSameParent); + collectDepthAsSpritesMenuItem.setVisible(allSelectedIsFrame && allSelectedSameParent); applyUnpackerMenu.setVisible(false); openSWFInsideTagMenuItem.setVisible(false); addAs12ScriptMenuItem.setVisible(false); @@ -1411,7 +1433,7 @@ public class TagTreeContextMenu extends JPopupMenu { replaceRefsWithTagMenuItem.setVisible(true); } } - + if (firstItem instanceof DefineSpriteTag) { replaceWithGifMenuItem.setVisible(true); } @@ -1636,6 +1658,10 @@ public class TagTreeContextMenu extends JPopupMenu { } } } + + if (allSelectedIsShape) { + convertShapeTypeMenuItem.setVisible(true); + } moveTagToMenu.removeAll(); moveTagToWithDependenciesMenu.removeAll(); @@ -2546,6 +2572,53 @@ public class TagTreeContextMenu extends JPopupMenu { mainPanel.refreshTree(swf); } } + + private void convertShapeTypeActionPerformed(ActionEvent evt) { + List itemr = getSelectedItems(); + if (itemr.isEmpty()) { + return; + } + int currentShapeNum = 0; + int min = 0; + int minForced = 0; + + ShapeTypeConverter converter = new ShapeTypeConverter(); + + + if (itemr.size() == 1) { + ShapeTag sh = (ShapeTag) itemr.get(0); + currentShapeNum = sh.getShapeNum(); + min = converter.getMinShapeNum(sh); + minForced = converter.getForcedMinShapeNum(sh); + } + + ConvertShapeTypeDialog dialog = new ConvertShapeTypeDialog(Main.getDefaultDialogsOwner(), currentShapeNum, minForced, min); + + int shapeNum = dialog.showDialog(); + + if (shapeNum == 0) { + return; + } + + for (TreeItem item : itemr) { + ShapeTag sh = (ShapeTag) item; + int newShapeNum = shapeNum; + int forcedMin = converter.getForcedMinShapeNum(sh); + if (newShapeNum < forcedMin) { + newShapeNum = forcedMin; + } + if (sh.getShapeNum() == newShapeNum) { + continue; + } + converter.convertCharacter(sh.getSwf(), sh.getCharacterId(), newShapeNum); + } + + mainPanel.refreshTree(); + if (itemr.size() == 1) { + ShapeTag sh = (ShapeTag) itemr.get(0); + mainPanel.setTagTreeSelectedNode(mainPanel.getCurrentTree(), sh.getSwf().getCharacter(sh.getCharacterId())); + } + } private void replaceRefsWithTagActionPerformed(ActionEvent evt) { TreeItem itemr = getCurrentItem();