Fixed: #2594 Export bounds for filtered sprites and buttons

This commit is contained in:
Jindra Petřík
2026-01-04 21:51:46 +01:00
parent b9e00fd673
commit 0ce2901b7c
20 changed files with 284 additions and 58 deletions

View File

@@ -161,6 +161,7 @@ import com.jpexs.decompiler.flash.types.SOUNDINFO;
import com.jpexs.decompiler.flash.types.annotations.Internal;
import com.jpexs.decompiler.flash.types.annotations.SWFField;
import com.jpexs.decompiler.flash.types.annotations.SWFType;
import com.jpexs.decompiler.flash.types.filters.FILTER;
import com.jpexs.decompiler.flash.types.sound.SoundInfoSoundCacheEntry;
import com.jpexs.decompiler.flash.xfl.FLAVersion;
import com.jpexs.decompiler.flash.xfl.XFLConverter;
@@ -186,6 +187,7 @@ import com.jpexs.helpers.SerializableImage;
import com.jpexs.helpers.utf8.Utf8Helper;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
@@ -5001,8 +5003,8 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
RECT rect = displayRect;
SerializableImage image = new SerializableImage(
rect.getWidth() == 0 ? 1 /*FIXME: is this necessary?*/ : (int) (rect.getWidth() * zoom * aaScale / SWF.unitDivisor),
rect.getHeight() == 0 ? 1 : (int) (rect.getHeight() * zoom * aaScale / SWF.unitDivisor), SerializableImage.TYPE_INT_ARGB_PRE);
rect.getWidth() == 0 ? 1 /*FIXME: is this necessary?*/ : (int) Math.ceil(rect.getWidth() * zoom * aaScale / SWF.unitDivisor),
rect.getHeight() == 0 ? 1 : (int) Math.ceil(rect.getHeight() * zoom * aaScale / SWF.unitDivisor), SerializableImage.TYPE_INT_ARGB_PRE);
if (backGroundColor == null) {
image.fillTransparent();
} else {
@@ -6425,4 +6427,14 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
//ignore
}
}
@Override
public Dimension getFilterDimensions() {
return new Dimension(0, 0);
}
@Override
public RECT getRectWithFilters() {
return getRect();
}
}

View File

@@ -41,7 +41,6 @@ import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.tags.base.CharacterTag;
import com.jpexs.decompiler.flash.tags.base.FontTag;
import com.jpexs.decompiler.flash.tags.base.RenderContext;
import com.jpexs.decompiler.flash.tags.base.StaticTextTag;
import com.jpexs.decompiler.flash.tags.base.TextTag;
import com.jpexs.decompiler.flash.tags.enums.ImageFormat;
import com.jpexs.decompiler.flash.timeline.DepthState;
@@ -248,8 +247,9 @@ public class FrameExporter {
int max = subFrameMode ? subframeLength : fframes.size();
int fframe = subFrameMode ? fframes.get(0) : fframes.get(pos++);
int realAaScale = Configuration.calculateRealAaScale(tim.displayRect.getWidth(), tim.displayRect.getHeight(), settings.zoom, settings.aaScale);
BufferedImage result = SWF.frameToImageGet(tim, fframe, subFrameMode ? pos++ : 0, null, 0, tim.displayRect, new Matrix(), null, backgroundColor == null && !usesTransparency ? Color.white : backgroundColor, settings.zoom, true, realAaScale).getBufferedImage();
RECT diplayRect = tim.getDisplayRectWithFilters();
int realAaScale = Configuration.calculateRealAaScale(diplayRect.getWidth(), diplayRect.getHeight(), settings.zoom, settings.aaScale);
BufferedImage result = SWF.frameToImageGet(tim, fframe, subFrameMode ? pos++ : 0, null, 0, diplayRect, new Matrix(), null, backgroundColor == null && !usesTransparency ? Color.white : backgroundColor, settings.zoom, true, realAaScale).getBufferedImage();
if (CancellableWorker.isInterrupted()) {
return null;
}

View File

@@ -248,15 +248,15 @@ public class DefineButton2Tag extends ButtonTag implements ASMSourceContainer {
}
double x = filter.getDeltaX();
double y = filter.getDeltaY();
deltaXMax += x * SWF.unitDivisor;
deltaYMax += y * SWF.unitDivisor;
deltaXMax += x;
deltaYMax += y;
}
}
r2.Xmin -= deltaXMax;
r2.Ymin -= deltaYMax;
r2.Xmax += deltaXMax;
r2.Ymax += deltaYMax;
r2.Xmin -= Math.ceil(deltaXMax) * SWF.unitDivisor;
r2.Ymin -= Math.ceil(deltaYMax) * SWF.unitDivisor;
r2.Xmax += Math.ceil(deltaXMax) * SWF.unitDivisor;
r2.Ymax += Math.ceil(deltaYMax) * SWF.unitDivisor;
rect.Xmin = Math.min(r2.Xmin, rect.Xmin);
rect.Ymin = Math.min(r2.Ymin, rect.Ymin);

View File

@@ -68,6 +68,7 @@ import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.Helper;
import com.jpexs.helpers.SerializableImage;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.io.IOException;
import java.io.StringReader;
@@ -1642,4 +1643,13 @@ public class DefineEditTextTag extends TextTag {
return ret;
}
@Override
public Dimension getFilterDimensions() {
return new Dimension(0, 0);
}
@Override
public RECT getRectWithFilters() {
return getRect();
}
}

View File

@@ -44,13 +44,12 @@ import com.jpexs.decompiler.flash.types.filters.FILTER;
import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.Cache;
import com.jpexs.helpers.SerializableImage;
import java.awt.Dimension;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -174,7 +173,7 @@ public class DefineSpriteTag extends DrawableTag implements Timelined {
@Override
public Timeline getTimeline() {
if (timeline == null) {
timeline = new Timeline(swf, this, spriteId, getRect());
timeline = new Timeline(swf, this, spriteId, getRect(), getFilterDimensions());
}
return timeline;
}
@@ -230,6 +229,67 @@ public class DefineSpriteTag extends DrawableTag implements Timelined {
return ret;
}
@Override
public Dimension getFilterDimensions() {
HashMap<Integer, Integer> depthMap = new HashMap<>();
int totalDeltaX = 0;
int totalDeltaY = 0;
for (Tag t : getTags()) {
if (t instanceof RemoveTag) {
RemoveTag rt = (RemoveTag) t;
depthMap.remove(rt.getDepth());
} else if (t instanceof PlaceObjectTypeTag) {
PlaceObjectTypeTag pot = (PlaceObjectTypeTag) t;
if (pot.flagMove() != depthMap.containsKey(pot.getDepth())) {
continue;
}
List<FILTER> filters = pot.getFilters();
int chId = pot.getCharacterId();
String chClass = pot.getClassName();
CharacterTag ch = null;
if (chId != -1) {
ch = swf.getCharacter(chId);
}
if (chClass != null) {
ch = swf.getCharacterByClass(chClass);
}
if (ch instanceof DrawableTag) {
Dimension filterDimension = ((DrawableTag) ch).getFilterDimensions();
totalDeltaX = Math.max(totalDeltaX, filterDimension.width);
totalDeltaY = Math.max(totalDeltaY, filterDimension.height);
}
double deltaXMax = 0;
double deltaYMax = 0;
if (filters != null && !filters.isEmpty()) {
// calculate size after applying the filters
for (FILTER filter : filters) {
if (!filter.enabled) {
continue;
}
double x = filter.getDeltaX();
double y = filter.getDeltaY();
deltaXMax += x;
deltaYMax += y;
}
totalDeltaX = Math.max(totalDeltaX, (int) (Math.ceil(deltaXMax) * SWF.unitDivisor));
totalDeltaY = Math.max(totalDeltaY, (int) (Math.ceil(deltaYMax) * SWF.unitDivisor));
}
}
}
return new Dimension(totalDeltaX, totalDeltaY);
}
@Override
public RECT getRect() {
return getRect(new HashSet<>());
@@ -250,8 +310,6 @@ public class DefineSpriteTag extends DrawableTag implements Timelined {
for (Tag t : getTags()) {
MATRIX m = null;
int characterId = -1;
double deltaXMax = 0;
double deltaYMax = 0;
if (t instanceof RemoveTag) {
RemoveTag rt = (RemoveTag) t;
depthMap.remove(rt.getDepth());
@@ -286,24 +344,7 @@ public class DefineSpriteTag extends DrawableTag implements Timelined {
characterId = chi;
}
}
List<FILTER> filters = pot.getFilters();
deltaXMax = 0;
deltaYMax = 0;
if (filters != null && !filters.isEmpty()) {
// calculate size after applying the filters
for (FILTER filter : filters) {
if (!filter.enabled) {
continue;
}
double x = filter.getDeltaX();
double y = filter.getDeltaY();
deltaXMax += x * SWF.unitDivisor;
deltaYMax += y * SWF.unitDivisor;
}
}
} else {
continue;
}
@@ -321,7 +362,7 @@ public class DefineSpriteTag extends DrawableTag implements Timelined {
RECT r = getCharacterBounds(need, added);
if (m != null) {
AffineTransform trans = SWF.matrixToTransform(m);
/*AffineTransform trans = SWF.matrixToTransform(m);
java.awt.Point topleft = new java.awt.Point();
trans.transform(new java.awt.Point(r.Xmin, r.Ymin), topleft);
@@ -335,13 +376,17 @@ public class DefineSpriteTag extends DrawableTag implements Timelined {
r.Xmin = (int) Math.min(Math.min(Math.min(topleft.x, topright.x), bottomleft.x), bottomright.x);
r.Ymin = (int) Math.min(Math.min(Math.min(topleft.y, topright.y), bottomleft.y), bottomright.y);
r.Xmax = (int) Math.max(Math.max(Math.max(topleft.x, topright.x), bottomleft.x), bottomright.x);
r.Ymax = (int) Math.max(Math.max(Math.max(topleft.y, topright.y), bottomleft.y), bottomright.y);
r.Ymax = (int) Math.max(Math.max(Math.max(topleft.y, topright.y), bottomleft.y), bottomright.y);*/
Matrix mat = new Matrix(m);
ExportRectangle r2 = mat.transform(new ExportRectangle(r));
r.Xmin = (int) r2.xMin;
r.Xmax = (int) r2.xMax;
r.Ymin = (int) r2.yMin;
r.Ymax = (int) r2.yMax;
//System.err.println("xmin = " + (r.Xmin / 20));
}
r.Xmin -= deltaXMax;
r.Ymin -= deltaYMax;
r.Xmax += deltaXMax;
r.Ymax += deltaYMax;
ret.Xmin = Math.min(r.Xmin, ret.Xmin);
ret.Ymin = Math.min(r.Ymin, ret.Ymin);

View File

@@ -50,6 +50,7 @@ import com.jpexs.video.SimpleMediaPlayer;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
@@ -541,7 +542,7 @@ public class DefineVideoStreamTag extends DrawableTag implements BoundedTag, Tim
}
this.tags = new ReadOnlyTagList(tags);
timeline = new Timeline(swf, this, characterID, getRect()) {
timeline = new Timeline(swf, this, characterID, getRect(), getFilterDimensions()) {
@Override
public void getSounds(int frame, int time, ButtonTag mouseOverButton, int mouseButton, List<Integer> sounds, List<String> soundClasses, List<SOUNDINFO> soundInfos) {
}
@@ -600,4 +601,8 @@ public class DefineVideoStreamTag extends DrawableTag implements BoundedTag, Tim
return numFrames;
}
@Override
public Dimension getFilterDimensions() {
return new Dimension(0, 0);
}
}

View File

@@ -17,6 +17,7 @@
package com.jpexs.decompiler.flash.tags.base;
import com.jpexs.decompiler.flash.types.RECT;
import java.awt.Dimension;
import java.util.Set;
/**
@@ -47,4 +48,18 @@ public interface BoundedTag {
* @return Rectangle including strokes
*/
public RECT getRectWithStrokes();
/**
* Gets filter dimensions.
*
* @return Filter dimensions
*/
public Dimension getFilterDimensions();
/**
* Gets rectangle including filters.
*
* @return RECT with filters applied
*/
public RECT getRectWithFilters();
}

View File

@@ -28,8 +28,10 @@ import com.jpexs.decompiler.flash.timeline.Timelined;
import com.jpexs.decompiler.flash.types.BUTTONRECORD;
import com.jpexs.decompiler.flash.types.ColorTransform;
import com.jpexs.decompiler.flash.types.RECT;
import com.jpexs.decompiler.flash.types.filters.FILTER;
import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.SerializableImage;
import java.awt.Dimension;
import java.awt.Shape;
import java.io.IOException;
import java.util.ArrayList;
@@ -173,7 +175,7 @@ public abstract class ButtonTag extends DrawableTag implements Timelined {
return timeline;
}
timeline = new Timeline(swf, this, getCharacterId(), getRect());
timeline = new Timeline(swf, this, getCharacterId(), getRect(), getFilterDimensions());
initTimeline(timeline);
return timeline;
}
@@ -400,4 +402,45 @@ public abstract class ButtonTag extends DrawableTag implements Timelined {
}
selectedRecord.fromPlaceObject(placeTag);
}
@Override
public Dimension getFilterDimensions() {
int totalDeltaX = 0;
int totalDeltaY = 0;
for (BUTTONRECORD rec : getRecords()) {
int chId = rec.characterId;
CharacterTag ch = null;
if (chId != -1) {
ch = swf.getCharacter(chId);
}
if (ch instanceof DrawableTag) {
Dimension filterDimension = ((DrawableTag) ch).getFilterDimensions();
totalDeltaX = Math.max(totalDeltaX, filterDimension.width);
totalDeltaY = Math.max(totalDeltaY, filterDimension.height);
}
double deltaXMax = 0;
double deltaYMax = 0;
if (rec.filterList != null && !rec.filterList.isEmpty()) {
// calculate size after applying the filters
for (FILTER filter : rec.filterList) {
if (!filter.enabled) {
continue;
}
double x = filter.getDeltaX();
double y = filter.getDeltaY();
deltaXMax += x;
deltaYMax += y;
}
totalDeltaX = Math.max(totalDeltaX, (int) (Math.ceil(deltaXMax) * SWF.unitDivisor));
totalDeltaY = Math.max(totalDeltaY, (int) (Math.ceil(deltaYMax) * SWF.unitDivisor));
}
}
return new Dimension(totalDeltaX, totalDeltaY);
}
}

View File

@@ -21,8 +21,10 @@ import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle;
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter;
import com.jpexs.decompiler.flash.types.ColorTransform;
import com.jpexs.decompiler.flash.types.RECT;
import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.SerializableImage;
import java.awt.Dimension;
import java.awt.Shape;
import java.io.IOException;
@@ -140,4 +142,15 @@ public abstract class DrawableTag extends CharacterTag implements BoundedTag {
* @return True if single frame, false if not
*/
public abstract boolean isSingleFrame();
@Override
public RECT getRectWithFilters() {
RECT r = new RECT(getRect());
Dimension filterDimension = getFilterDimensions();
r.Xmin -= filterDimension.width;
r.Xmax += filterDimension.width;
r.Ymin -= filterDimension.height;
r.Ymax += filterDimension.height;
return r;
}
}

View File

@@ -36,6 +36,7 @@ import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.Helper;
import com.jpexs.helpers.SerializableImage;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Rectangle;
import java.awt.Shape;
@@ -857,4 +858,9 @@ public abstract class FontTag extends DrawableTag implements AloneTag {
* Update tables of bounds
*/
public abstract void updateBounds();
@Override
public Dimension getFilterDimensions() {
return new Dimension(0, 0);
}
}

View File

@@ -420,4 +420,9 @@ public abstract class ImageTag extends DrawableTag {
return true;
}
@Override
public Dimension getFilterDimensions() {
return new Dimension(0, 0);
}
}

View File

@@ -43,6 +43,7 @@ import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord;
import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.SerializableImage;
import java.awt.Dimension;
import java.awt.Shape;
import java.util.ArrayList;
import java.util.HashSet;
@@ -478,4 +479,9 @@ public abstract class MorphShapeTag extends DrawableTag {
updateStartBounds();
updateEndBounds();
}
@Override
public Dimension getFilterDimensions() {
return new Dimension(0, 0);
}
}

View File

@@ -38,6 +38,7 @@ import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord;
import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.SerializableImage;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
@@ -316,4 +317,9 @@ public abstract class ShapeTag extends DrawableTag implements LazyObject {
shapes.clearCachedOutline();
shapeBounds = SHAPERECORD.getBounds(shapes.shapeRecords, shapes.lineStyles, getShapeNum(), false);
}
@Override
public Dimension getFilterDimensions() {
return new Dimension(0, 0);
}
}

View File

@@ -44,6 +44,7 @@ import com.jpexs.decompiler.flash.types.annotations.SWFType;
import com.jpexs.helpers.ByteArrayRange;
import com.jpexs.helpers.Helper;
import com.jpexs.helpers.SerializableImage;
import java.awt.Dimension;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
@@ -1065,4 +1066,9 @@ public abstract class StaticTextTag extends TextTag {
public void toHtmlCanvas(StringBuilder result, double unitDivisor) {
staticTextToHtmlCanvas(unitDivisor, swf, textRecords, getTextNum(), result, textBounds, textMatrix, null);
}
@Override
public Dimension getFilterDimensions() {
return new Dimension(0, 0);
}
}

View File

@@ -79,6 +79,7 @@ import com.jpexs.helpers.SerializableImage;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
@@ -127,6 +128,11 @@ public class Timeline {
* Display rect.
*/
public RECT displayRect;
/**
* Filters dimension.
*/
public Dimension filterDimension;
/**
* Frame rate.
@@ -465,7 +471,7 @@ public class Timeline {
* @param swf SWF
*/
public Timeline(SWF swf) {
this(swf, swf, 0, swf.displayRect);
this(swf, swf, 0, swf.displayRect, swf.getFilterDimensions());
}
/**
@@ -475,13 +481,15 @@ public class Timeline {
* @param timelined Timelined
* @param id Timeline id - usually characterId or 0 for SWF main timeline
* @param displayRect Display rect
* @param filterDimension Filters dimension
*/
public Timeline(SWF swf, Timelined timelined, int id, RECT displayRect) {
public Timeline(SWF swf, Timelined timelined, int id, RECT displayRect, Dimension filterDimension) {
this.id = id;
this.swf = swf;
this.displayRect = displayRect;
this.frameRate = swf.frameRate;
this.timelined = timelined;
this.filterDimension = filterDimension;
as2RootPackage = new AS2Package(null, null, swf, false, false);
}
@@ -1159,14 +1167,14 @@ public class Timeline {
deltaXMax += x;
deltaYMax += y;
}
rect.xMin -= deltaXMax * unzoom * SWF.unitDivisor;
rect.xMax += deltaXMax * unzoom * SWF.unitDivisor;
rect.yMin -= deltaYMax * unzoom * SWF.unitDivisor;
rect.yMax += deltaYMax * unzoom * SWF.unitDivisor;
viewRect2.xMin -= deltaXMax * SWF.unitDivisor;
viewRect2.xMax += deltaXMax * SWF.unitDivisor;
viewRect2.yMin -= deltaYMax * SWF.unitDivisor;
viewRect2.yMax += deltaYMax * SWF.unitDivisor;
rect.xMin -= Math.ceil(deltaXMax * unzoom) * SWF.unitDivisor;
rect.xMax += Math.ceil(deltaXMax * unzoom) * SWF.unitDivisor;
rect.yMin -= Math.ceil(deltaYMax * unzoom) * SWF.unitDivisor;
rect.yMax += Math.ceil(deltaYMax * unzoom) * SWF.unitDivisor;
viewRect2.xMin -= Math.ceil(deltaXMax) * SWF.unitDivisor;
viewRect2.xMax += Math.ceil(deltaXMax) * SWF.unitDivisor;
viewRect2.yMin -= Math.ceil(deltaYMax) * SWF.unitDivisor;
viewRect2.yMax += Math.ceil(deltaYMax) * SWF.unitDivisor;
}
drawMatrix.translate(rect.xMin, rect.yMin);
@@ -2263,4 +2271,18 @@ public class Timeline {
ensureInitialized();
return new ArrayList<>(scenes);
}
/**
* Gets display rect including filters.
*
* @return RECT with filters applied
*/
public RECT getDisplayRectWithFilters() {
RECT r = new RECT(displayRect);
r.Xmin -= filterDimension.width;
r.Xmax += filterDimension.width;
r.Ymin -= filterDimension.height;
r.Ymax += filterDimension.height;
return r;
}
}