feat: add advanced multi sample anti-aliased shape renderer

Multi sample anti-alias renderer with configurable grid.
It can be turned on with icon under render window,
and with checkbox for export.
This commit is contained in:
Jindra Petřík
2026-04-03 08:11:19 +02:00
parent 0938f5cbad
commit 74b4e957a6
34 changed files with 2176 additions and 182 deletions

View File

@@ -5035,8 +5035,8 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
int originalHeight = (int) Math.ceil(rect.getHeight() * zoom / SWF.unitDivisor);
SerializableImage image = new SerializableImage(
originalWidth * aaScale,
originalHeight * aaScale,
originalWidth,
originalHeight,
SerializableImage.TYPE_INT_ARGB_PRE
);
if (backGroundColor == null) {
@@ -5049,25 +5049,17 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
}
Matrix m = transformation.clone();
m.translate(-rect.Xmin * zoom * aaScale, -rect.Ymin * zoom * aaScale);
m.scale(zoom * aaScale);
m.translate(-rect.Xmin * zoom, -rect.Ymin * zoom);
m.scale(zoom);
RenderContext renderContext = new RenderContext();
renderContext.cursorPosition = cursorPosition;
renderContext.mouseButton = mouseButton;
ExportRectangle viewRect = new ExportRectangle(rect);
viewRect.xMin *= aaScale;
viewRect.yMin *= aaScale;
viewRect.xMax *= aaScale;
viewRect.yMax *= aaScale;
timeline.toImage(frame, time, renderContext, image, image, false, m, new Matrix(), m, colorTransform, zoom * aaScale, true, viewRect, viewRect, m, true, Timeline.DRAW_MODE_ALL, 0, canUseSmoothing, new ArrayList<>(), aaScale);
if (aaScale > 1) {
image = new SerializableImage(ImageResizer.resizeImage(image.getBufferedImage(), originalWidth, originalHeight, RenderingHints.VALUE_INTERPOLATION_BICUBIC, true));
}
timeline.toImage(frame, time, renderContext, image, image, false, m, new Matrix(), m, colorTransform, zoom, true, viewRect, viewRect, m, true, Timeline.DRAW_MODE_ALL, 0, canUseSmoothing, new ArrayList<>(), aaScale);
return image;
}
@@ -6476,6 +6468,6 @@ public final class SWF implements SWFContainerItem, Timelined, Openable {
@Override
public RECT getRectWithFilters() {
return getRect();
return getRectWithStrokes();
}
}

View File

@@ -844,18 +844,22 @@ public final class Configuration {
@ConfigurationDefaultBoolean(false)
@ConfigurationCategory("display")
@ConfigurationRemoved
public static ConfigurationItem<Boolean> reduceAntialiasConflationByScalingForDisplay = null;
@ConfigurationDefaultInt(4)
@ConfigurationCategory("display")
@ConfigurationRemoved
public static ConfigurationItem<Integer> reduceAntialiasConflationByScalingValueForDisplay = null;
@ConfigurationDefaultBoolean(false)
@ConfigurationCategory("export")
@ConfigurationRemoved
public static ConfigurationItem<Boolean> reduceAntialiasConflationByScalingForExport = null;
@ConfigurationDefaultInt(10)
@ConfigurationDefaultInt(4)
@ConfigurationCategory("export")
@ConfigurationRemoved
public static ConfigurationItem<Integer> reduceAntialiasConflationByScalingValueForExport = null;
@ConfigurationDefaultBoolean(true)
@@ -1213,6 +1217,22 @@ public final class Configuration {
@ConfigurationCategory("export")
public static ConfigurationItem<Boolean> exportFlaAs3DisableScriptLayer = null;
@ConfigurationDefaultBoolean(false)
@ConfigurationCategory("display")
public static ConfigurationItem<Boolean> useMsaaForDisplay = null;
@ConfigurationDefaultBoolean(false)
@ConfigurationCategory("export")
public static ConfigurationItem<Boolean> useMsaaForExport = null;
@ConfigurationDefaultInt(4)
@ConfigurationCategory("display")
public static ConfigurationItem<Integer> msaaGridForDisplay = null;
@ConfigurationDefaultInt(4)
@ConfigurationCategory("export")
public static ConfigurationItem<Integer> msaaGridForExport = null;
private static Map<String, String> configurationDescriptions = new LinkedHashMap<>();
private static Map<String, String> configurationTitles = new LinkedHashMap<>();
@@ -1865,20 +1885,5 @@ public final class Configuration {
}
return null;
}
public static int calculateRealAaScale(int imageWidth, int imageHeight, double zoom, int initialAaScale) {
final int MAX_IMAGE_DIMENSION = 10000;
int aaScale = initialAaScale;
while (
aaScale > 1
&& (((long) imageWidth * zoom * aaScale / SWF.unitDivisor) > MAX_IMAGE_DIMENSION
|| ((long) imageHeight * zoom * aaScale / SWF.unitDivisor) > MAX_IMAGE_DIMENSION)
) {
aaScale--;
}
return aaScale;
}
}
}

View File

@@ -268,8 +268,7 @@ public class FrameExporter {
int fframe = subFrameMode ? fframes.get(0) : fframes.get(pos++);
RECT displayRect = tim.getDisplayRectWithFilters();
int realAaScale = Configuration.calculateRealAaScale(displayRect.getWidth(), displayRect.getHeight(), settings.zoom, settings.aaScale);
BufferedImage result = SWF.frameToImageGet(tim, fframe, subFrameMode ? pos++ : 0, null, 0, displayRect, new Matrix(), null, backgroundColor == null && !usesTransparency ? Color.white : backgroundColor, settings.zoom, true, realAaScale).getBufferedImage();
BufferedImage result = SWF.frameToImageGet(tim, fframe, subFrameMode ? pos++ : 0, null, 0, displayRect, new Matrix(), null, backgroundColor == null && !usesTransparency ? Color.white : backgroundColor, settings.zoom, true, settings.aaScale).getBufferedImage();
if (CancellableWorker.isInterrupted()) {
return null;
}

View File

@@ -171,13 +171,11 @@ public class MorphShapeExporter {
st = mst.getStartShapeTag();
rect = st.getRect();
int realAaScale = Configuration.calculateRealAaScale(rect.getWidth(), rect.getHeight(), settings.zoom, settings.aaScale);
int originalWidth = (int) Math.ceil(rect.getWidth() * settings.zoom / SWF.unitDivisor);
int originalHeight = (int) Math.ceil(rect.getHeight() * settings.zoom / SWF.unitDivisor);
int newWidth = originalWidth * realAaScale;
int newHeight = originalHeight * realAaScale;
int newWidth = originalWidth;
int newHeight = originalHeight;
SerializableImage img = new SerializableImage(newWidth, newHeight, SerializableImage.TYPE_INT_ARGB_PRE);
img.fillTransparent();
if (settings.mode == MorphShapeExportMode.BMP_START_END) {
@@ -188,16 +186,11 @@ public class MorphShapeExporter {
g.fillRect(0, 0, img.getWidth(), img.getHeight());
}
}
m = Matrix.getScaleInstance(settings.zoom * realAaScale);
m = Matrix.getScaleInstance(settings.zoom);
m.translate(-rect.Xmin, -rect.Ymin);
st.toImage(0, 0, 0, new RenderContext(), img, img, false, m, m, m, m, new CXFORMWITHALPHA(), unzoom * realAaScale, false, new ExportRectangle(rect), new ExportRectangle(rect), true, Timeline.DRAW_MODE_ALL, 0, true, realAaScale);
st.toImage(0, 0, 0, new RenderContext(), img, img, false, m, m, m, m, new CXFORMWITHALPHA(), unzoom, false, new ExportRectangle(rect), new ExportRectangle(rect), true, Timeline.DRAW_MODE_ALL, 0, true, settings.aaScale);
BufferedImage bim = img.getBufferedImage();
if (realAaScale > 1) {
bim = ImageResizer.resizeImage(bim, originalWidth, originalHeight, RenderingHints.VALUE_INTERPOLATION_BICUBIC, true);
}
if (settings.mode == MorphShapeExportMode.PNG_START_END) {
ImageHelper.write(bim, ImageFormat.PNG, fileStart);
} else if (settings.mode == MorphShapeExportMode.WEBP_START_END) {
@@ -210,13 +203,12 @@ public class MorphShapeExporter {
st = mst.getEndShapeTag();
rect = st.getRect();
realAaScale = Configuration.calculateRealAaScale(rect.getWidth(), rect.getHeight(), settings.zoom, settings.aaScale);
originalWidth = (int) Math.ceil(rect.getWidth() * settings.zoom / SWF.unitDivisor);
originalHeight = (int) Math.ceil(rect.getHeight() * settings.zoom / SWF.unitDivisor);
newWidth = originalWidth * realAaScale;
newHeight = originalHeight * realAaScale;
newWidth = originalWidth;
newHeight = originalHeight;
img = new SerializableImage(newWidth, newHeight, SerializableImage.TYPE_INT_ARGB_PRE);
img.fillTransparent();
@@ -228,16 +220,12 @@ public class MorphShapeExporter {
g.fillRect(0, 0, img.getWidth(), img.getHeight());
}
}
m = Matrix.getScaleInstance(settings.zoom * realAaScale);
m = Matrix.getScaleInstance(settings.zoom);
m.translate(-rect.Xmin, -rect.Ymin);
st.toImage(0, 0, 0, new RenderContext(), img, img, false, m, m, m, m, new CXFORMWITHALPHA(), unzoom, false, new ExportRectangle(rect), new ExportRectangle(rect), true, Timeline.DRAW_MODE_ALL, 0, true, settings.aaScale);
bim = img.getBufferedImage();
if (realAaScale > 1) {
bim = ImageResizer.resizeImage(bim, originalWidth, originalHeight, RenderingHints.VALUE_INTERPOLATION_BICUBIC, true);
}
if (settings.mode == MorphShapeExportMode.PNG_START_END) {
ImageHelper.write(bim, ImageFormat.PNG, fileEnd);
} else if (settings.mode == MorphShapeExportMode.WEBP_START_END) {
@@ -452,11 +440,8 @@ public class MorphShapeExporter {
private final Color backgroundColor;
private final MorphShapeExportSettings settings;
private final RECT rect;
private final int realAaScale;
private final int originalWidth;
private final int originalHeight;
private final int newWidth;
private final int newHeight;
public MyFrameIterator(
MorphShapeTag mst,
@@ -474,11 +459,8 @@ public class MorphShapeExporter {
this.settings = settings;
rect = mst.getRect();
realAaScale = Configuration.calculateRealAaScale(rect.getWidth(), rect.getHeight(), settings.zoom, settings.aaScale);
originalWidth = (int) Math.ceil(rect.getWidth() * settings.zoom / SWF.unitDivisor);
originalHeight = (int) Math.ceil(rect.getHeight() * settings.zoom / SWF.unitDivisor);
newWidth = originalWidth * realAaScale;
newHeight = originalHeight * realAaScale;
}
public void reset() {
@@ -514,7 +496,7 @@ public class MorphShapeExporter {
ShapeTag st = mst.getShapeTagAtRatio(ratio);
SerializableImage img = new SerializableImage(newWidth, newHeight, SerializableImage.TYPE_INT_ARGB_PRE);
SerializableImage img = new SerializableImage(originalWidth, originalHeight, SerializableImage.TYPE_INT_ARGB_PRE);
img.fillTransparent();
if (!usesTransparency) {
Graphics2D g = (Graphics2D) img.getGraphics();
@@ -525,16 +507,12 @@ public class MorphShapeExporter {
}
g.fillRect(0, 0, img.getWidth(), img.getHeight());
}
Matrix m = Matrix.getScaleInstance(settings.zoom * realAaScale);
Matrix m = Matrix.getScaleInstance(settings.zoom);
m.translate(-rect.Xmin, -rect.Ymin);
st.toImage(0, 0, 0, new RenderContext(), img, img, false, m, m, m, m, new CXFORMWITHALPHA(), settings.zoom * realAaScale, false, new ExportRectangle(rect), new ExportRectangle(rect), true, Timeline.DRAW_MODE_ALL, 0, true, realAaScale);
st.toImage(0, 0, 0, new RenderContext(), img, img, false, m, m, m, m, new CXFORMWITHALPHA(), settings.zoom, false, new ExportRectangle(rect), new ExportRectangle(rect), true, Timeline.DRAW_MODE_ALL, 0, true, settings.aaScale);
BufferedImage result = img.getBufferedImage();
if (realAaScale > 1) {
result = ImageResizer.resizeImage(result, originalWidth, originalHeight, RenderingHints.VALUE_INTERPOLATION_BICUBIC, true);
}
if (CancellableWorker.isInterrupted()) {
return null;
}

View File

@@ -132,11 +132,10 @@ public class ShapeExporter {
case PNG:
case BMP:
case WEBP:
int realAaScale = Configuration.calculateRealAaScale(rect.getWidth(), rect.getHeight(), settings.zoom, aaScale);
int originalWidth = (int) Math.ceil(rect.getWidth() * settings.zoom / SWF.unitDivisor);
int originalHeight = (int) Math.ceil(rect.getHeight() * settings.zoom / SWF.unitDivisor);
int newWidth = originalWidth * realAaScale;
int newHeight = originalHeight * realAaScale;
int newWidth = originalWidth;
int newHeight = originalHeight;
SerializableImage img = new SerializableImage(newWidth, newHeight, SerializableImage.TYPE_INT_ARGB_PRE);
img.fillTransparent();
if (settings.mode == ShapeExportMode.BMP) {
@@ -147,15 +146,13 @@ public class ShapeExporter {
g.fillRect(0, 0, img.getWidth(), img.getHeight());
}
}
Matrix m2 = Matrix.getScaleInstance(settings.zoom * realAaScale);
Matrix m2 = Matrix.getScaleInstance(settings.zoom);
m2.translate(-rect.Xmin, -rect.Ymin);
st.toImage(0, 0, 0, new RenderContext(), img, img, false, m2, m2, m2, m2, new CXFORMWITHALPHA(), unzoom * realAaScale, false, new ExportRectangle(rect), new ExportRectangle(rect), true, Timeline.DRAW_MODE_ALL, 0, true, realAaScale);
st.toImage(0, 0, 0, new RenderContext(), img, img, false, m2, m2, m2, m2, new CXFORMWITHALPHA(), unzoom, false, new ExportRectangle(rect), new ExportRectangle(rect), true, Timeline.DRAW_MODE_ALL, 0, true, aaScale);
BufferedImage bim = img.getBufferedImage();
if (realAaScale > 1) {
bim = ImageResizer.resizeImage(bim, originalWidth, originalHeight, RenderingHints.VALUE_INTERPOLATION_BICUBIC, true);
}
if (settings.mode == ShapeExportMode.PNG) {
ImageHelper.write(bim, ImageFormat.PNG, file);

View File

@@ -17,10 +17,10 @@
package com.jpexs.decompiler.flash.exporters.shape;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.exporters.ImageTagBufferedImage;
import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle;
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
import com.jpexs.decompiler.flash.exporters.shape.aa.AntialiasedBitmapExporter;
import com.jpexs.decompiler.flash.tags.base.ImageTag;
import com.jpexs.decompiler.flash.tags.base.ShapeTag;
import com.jpexs.decompiler.flash.types.ColorTransform;
@@ -66,49 +66,49 @@ public class BitmapExporter extends ShapeExporterBase {
private static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform();
private SerializableImage image;
protected SerializableImage image;
private Graphics2D graphics;
protected Graphics2D graphics;
private final Color defaultColor;
protected final Color defaultColor;
private final SWF swf;
protected final SWF swf;
private final GeneralPath path;
protected final GeneralPath path;
private Paint fillPaint;
protected Paint fillPaint;
private boolean fillRepeat;
protected boolean fillRepeat;
private boolean fillSmooth;
protected boolean fillSmooth;
private AffineTransform fillTransform;
protected AffineTransform fillTransform;
private Paint linePaint;
protected Paint linePaint;
private AffineTransform lineTransform;
protected AffineTransform lineTransform;
private Color lineColor;
protected Color lineColor;
private Stroke lineStroke;
protected Stroke lineStroke;
private Stroke defaultStroke;
protected Stroke defaultStroke;
private Matrix strokeTransformation;
protected Matrix strokeTransformation;
private double thicknessScale;
private double thicknessScaleX;
private double thicknessScaleY;
protected double thicknessScale;
protected double thicknessScaleX;
protected double thicknessScaleY;
private double unzoom;
protected double unzoom;
private int aaScale;
protected int aaScale;
private static boolean linearGradientColorWarningShown = false;
protected static boolean linearGradientColorWarningShown = false;
private boolean scaleStrokes;
private class TransformedStroke implements Stroke {
protected boolean scaleStrokes;
protected class TransformedStroke implements Stroke {
/**
* To make this serializable without problems.
@@ -176,41 +176,49 @@ public class BitmapExporter extends ShapeExporterBase {
* @param aaScale Antialias conflation reducing scale coefficient
*/
public static void export(int windingRule, int shapeNum, SWF swf, SHAPE shape, Color defaultColor, SerializableImage image, double unzoom, Matrix transformation, Matrix strokeTransformation, ColorTransform colorTransform, boolean scaleStrokes, boolean canUseSmoothing, int aaScale) {
BitmapExporter exporter = new BitmapExporter(windingRule, shapeNum, swf, shape, defaultColor, colorTransform);
exporter.setCanUseSmoothing(canUseSmoothing);
exporter.exportTo(shapeNum, image, unzoom, transformation, strokeTransformation, scaleStrokes, aaScale);
if (aaScale > 1) {
AntialiasedBitmapExporter.export(windingRule, shapeNum, swf, shape, defaultColor, image, unzoom, transformation, strokeTransformation, colorTransform, scaleStrokes, canUseSmoothing, aaScale);
} else {
BitmapExporter exporter = new BitmapExporter(windingRule, shapeNum, swf, shape, defaultColor, colorTransform);
exporter.setCanUseSmoothing(canUseSmoothing);
exporter.exportTo(shapeNum, image, unzoom, transformation, strokeTransformation, scaleStrokes, aaScale);
}
}
private BitmapExporter(int windingRule, int shapeNum, SWF swf, SHAPE shape, Color defaultColor, ColorTransform colorTransform) {
protected BitmapExporter(int windingRule, int shapeNum, SWF swf, SHAPE shape, Color defaultColor, ColorTransform colorTransform) {
super(windingRule, shapeNum, swf, shape, colorTransform);
this.swf = swf;
this.defaultColor = defaultColor;
path = new GeneralPath(windingRule == ShapeTag.WIND_NONZERO ? GeneralPath.WIND_NON_ZERO : GeneralPath.WIND_EVEN_ODD);
}
private void exportTo(int shapeNum, SerializableImage image, double unzoom, Matrix transformation, Matrix strokeTransformation, boolean scaleStrokes, int aaScale) {
protected void exportTo(int shapeNum, SerializableImage image, double unzoom, Matrix transformation, Matrix strokeTransformation, boolean scaleStrokes, int aaScale) {
this.image = image;
this.scaleStrokes = scaleStrokes;
ExportRectangle bounds = new ExportRectangle(shape.getBounds(shapeNum));
this.strokeTransformation = strokeTransformation;
calculateThicknessScale(bounds, transformation);
calculateThicknessScale();
graphics = (Graphics2D) image.getGraphics();
AffineTransform at = transformation.toTransform();
at.preConcatenate(AffineTransform.getScaleInstance(1 / SWF.unitDivisor, 1 / SWF.unitDivisor));
graphics.setTransform(at);
setTransform(graphics, transformation);
defaultStroke = graphics.getStroke();
this.unzoom = unzoom;
this.aaScale = aaScale;
super.export();
}
protected void setTransform(Graphics2D g, Matrix transformation) {
AffineTransform at = transformation.toTransform();
at.preConcatenate(AffineTransform.getScaleInstance(1 / SWF.unitDivisor, 1 / SWF.unitDivisor));
graphics.setTransform(at);
}
private void calculateThicknessScale(ExportRectangle bounds, Matrix transformation) {
protected void calculateThicknessScale() {
com.jpexs.decompiler.flash.exporters.commonshape.Point p00 = strokeTransformation.transform(0, 0);
com.jpexs.decompiler.flash.exporters.commonshape.Point p11 = strokeTransformation.transform(1, 1);
thicknessScale = p00.distanceTo(p11) / Math.sqrt(2);
thicknessScaleX = Math.abs(p11.x - p00.x);
thicknessScaleY = Math.abs(p11.y - p00.y);
thicknessScaleY = Math.abs(p11.y - p00.y);
}
/**
@@ -417,19 +425,9 @@ public class BitmapExporter extends ShapeExporterBase {
//always display minimum stroke of 1 pixel, no matter how zoomed it is
if (thickness * unzoom / aaScale < 1 * SWF.unitDivisor) {
thickness = 1 * SWF.unitDivisor / (unzoom / aaScale);
}
/*
if (Configuration.fixAntialiasConflation.get()) {
thickness += 1 * SWF.unitDivisor / unzoom;
}
*/
}
if (joinStyle == BasicStroke.JOIN_MITER) {
//lineStroke = new BasicStroke((float) thickness, capStyle, joinStyle, miterLimit);
/*if (Configuration.allowMiterClipLinestyle.get()) {
lineStroke = new MiterClipBasicStroke((BasicStroke) lineStroke);
}*/
lineStroke = new ExtendedBasicStroke((float) thickness, capStyle, ExtendedBasicStroke.JOIN_MITER_CLIP, miterLimit);
} else {
lineStroke = new BasicStroke((float) thickness, capStyle, joinStyle);

View File

@@ -0,0 +1,426 @@
package com.jpexs.decompiler.flash.exporters.shape.aa;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle;
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter;
import com.jpexs.decompiler.flash.types.ColorTransform;
import com.jpexs.decompiler.flash.types.LINESTYLE2;
import com.jpexs.decompiler.flash.types.RECT;
import com.jpexs.decompiler.flash.types.RGB;
import com.jpexs.decompiler.flash.types.SHAPE;
import com.jpexs.graphics.ExtendedBasicStroke;
import com.jpexs.helpers.SerializableImage;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.MultipleGradientPaint;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.TexturePaint;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.util.ArrayList;
import java.util.List;
/**
* Antialiased Shape Bitmap Exporter
*
* @author JPEXS
*/
public class AntialiasedBitmapExporter extends BitmapExporter {
private final List<List<Vec2>> contours = new ArrayList<>();
private AntialiasTools.SceneRasterizerMSAA rz;
private Vec2 lastPos = null;
private Matrix vecTrans;
private Matrix vecTransLines;
private ExportRectangle viewRect;
private boolean inLines = false;
private boolean closeLine = false;
private double curveFlattness = 1.0;
/**
* Exports a shape to a bitmap.
*
* @param windingRule Winding rule
* @param shapeNum Shape number
* @param swf SWF
* @param shape Shape
* @param defaultColor Default color
* @param image Image
* @param unzoom Unzoom
* @param transformation Transformation
* @param strokeTransformation Stroke transformation
* @param colorTransform Color transform
* @param scaleStrokes Scale strokes
* @param canUseSmoothing Can use smoothing
* @param aaScale Antialias conflation reducing scale coefficient
*/
public static void export(int windingRule, int shapeNum, SWF swf, SHAPE shape, Color defaultColor, SerializableImage image, double unzoom, Matrix transformation, Matrix strokeTransformation, ColorTransform colorTransform, boolean scaleStrokes, boolean canUseSmoothing, int aaScale) {
AntialiasedBitmapExporter exporter = new AntialiasedBitmapExporter(windingRule, shapeNum, swf, shape, defaultColor, colorTransform, (int) Math.round(20 / unzoom));
exporter.setCanUseSmoothing(canUseSmoothing);
exporter.exportTo(shapeNum, image, unzoom, transformation, strokeTransformation, scaleStrokes, aaScale);
}
protected AntialiasedBitmapExporter(int windingRule, int shapeNum, SWF swf, SHAPE shape, Color defaultColor, ColorTransform colorTransform, int thicknessDivisor) {
super(windingRule, shapeNum, swf, shape, defaultColor, colorTransform);
}
@Override
protected void exportTo(int shapeNum, SerializableImage image, double unzoom, Matrix transformation, Matrix strokeTransformation, boolean scaleStrokes, int aaScale) {
this.strokeTransformation = strokeTransformation;
calculateThicknessScale();
//double s = Math.max(transformation.scaleX,transformation.scaleY);
RECT bounds = new RECT(shape.getBounds(shapeNum));
int maxStrokeWidth = shape.getMaxStrokeWidth(shapeNum);
bounds.Xmin -= maxStrokeWidth;
bounds.Ymin -= maxStrokeWidth;
bounds.Xmax += maxStrokeWidth;
bounds.Ymax += maxStrokeWidth;
vecTrans = transformation.preConcatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor));
viewRect = vecTrans.transform(new ExportRectangle(bounds));
/*viewRect.xMin -= 1;
viewRect.yMin -= 1;
viewRect.yMax += 1;
viewRect.xMax += 1;*/
viewRect.xMin = Math.floor(viewRect.xMin - unzoom - 0.5);
viewRect.yMin = Math.floor(viewRect.yMin - unzoom - 0.5);
viewRect.xMax = Math.ceil(viewRect.xMax + unzoom);
viewRect.yMax = Math.ceil(viewRect.yMax + unzoom);
int width = (int) viewRect.getWidth();
int height = (int) viewRect.getHeight();
vecTrans = vecTrans.preConcatenate(Matrix.getTranslateInstance(-viewRect.xMin, -viewRect.yMin));
vecTransLines = vecTrans.preConcatenate(Matrix.getTranslateInstance(-0.5, -0.5));
Graphics2D g = (Graphics2D) image.getGraphics();
//g.setTransform(t);
if (width <= 0 || height <= 0) {
return;
}
curveFlattness = 1.0 / aaScale;
BufferedImage image2 = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
Graphics2D g2 = (Graphics2D) image2.getGraphics();
g2.setComposite(AlphaComposite.Src);
g2.setColor(new Color(0, 0, 0, 0));
g2.fillRect(0, 0, width, height);
try {
rz = new AntialiasTools.SceneRasterizerMSAA(width, height, aaScale);
} catch (OutOfMemoryError er) {
System.gc();
return;
}
rz.clear(0x00000000);
Shape clip = g.getClip();
if (clip != null) {
AffineTransform t = new AffineTransform(g.getTransform());
t.preConcatenate(AffineTransform.getTranslateInstance(-viewRect.xMin, -viewRect.yMin));
clip = t.createTransformedShape(clip);
List<List<Vec2>> clipContours = AntialiasTools.shapeToContours(clip, curveFlattness);
rz.setClipContours(clipContours, Path2D.WIND_EVEN_ODD);
}
this.image = image;
this.scaleStrokes = scaleStrokes;
graphics = (Graphics2D) image.getGraphics();
setTransform(graphics, transformation);
defaultStroke = graphics.getStroke();
this.unzoom = unzoom;
this.aaScale = 1; //aaScale;
super.export();
//super.exportTo(shapeNum, image, unzoom, transformation, strokeTransformation, scaleStrokes, aaScale);
//rz.resolveToReplace(image2);
rz.resolveTo(image2);
g.setComposite(AlphaComposite.SrcOver);
g.setTransform(new AffineTransform());
g.drawImage(image2, (int) viewRect.xMin, (int) viewRect.yMin, null);
/*
g.setColor(new Color(0,0,0,128));
g.setStroke(new BasicStroke(1));
g.drawRect((int) viewRect.xMin, (int) viewRect.yMin, (int) viewRect.getWidth(), (int) viewRect.getHeight());
*/
}
@Override
protected void setTransform(Graphics2D g, Matrix transformation) {
AffineTransform at = transformation.toTransform();
at.preConcatenate(AffineTransform.getScaleInstance(1 / SWF.unitDivisor, 1 / SWF.unitDivisor));
at.preConcatenate(AffineTransform.getTranslateInstance(-viewRect.xMin, -viewRect.yMin));
graphics.setTransform(at);
}
/**
* Returns the image.
*
* @return Image
*/
public SerializableImage getImage() {
return image;
}
@Override
public void beginShape() {
}
@Override
public void endShape() {
}
@Override
public void beginFills() {
}
@Override
public void endFills() {
}
@Override
public void beginLines() {
inLines = true;
}
@Override
public void endLines(boolean close) {
closeLine = close;
/*if (close) {
//path.closePath();
}*/
finalizePath();
inLines = false;
}
@Override
public void moveTo(double x, double y) {
Point2D src = new Point2D.Double(x, y);
Point2D dst = new Point2D.Double();
dst = (inLines ? vecTransLines : vecTrans).transform(src);
contours.add(new ArrayList<>());
lastPos = new Vec2(dst.getX(), dst.getY());
contours.get(contours.size() - 1).add(lastPos);
}
@Override
public void lineTo(double x, double y) {
Point2D src = new Point2D.Double(x, y);
Point2D dst = new Point2D.Double();
dst = (inLines ? vecTransLines : vecTrans).transform(src);
lastPos = new Vec2(dst.getX(), dst.getY());
contours.get(contours.size() - 1).add(lastPos);
}
@Override
public void curveTo(double controlX, double controlY, double anchorX, double anchorY) {
Point2D src = new Point2D.Double(controlX, controlY);
Point2D dst = new Point2D.Double();
dst = (inLines ? vecTransLines : vecTrans).transform(src);
Vec2 controlVec2 = new Vec2(dst.getX(), dst.getY());
src = new Point2D.Double(anchorX, anchorY);
dst = (inLines ? vecTransLines : vecTrans).transform(src);
Vec2 anchorVec2 = new Vec2(dst.getX(), dst.getY());
List<Vec2> contour = contours.get(contours.size() - 1);
AntialiasTools.append(contour, AntialiasTools.flattenQuadraticBezier(lastPos, controlVec2, anchorVec2, curveFlattness), true);
lastPos = anchorVec2;
}
private void drawImage(BufferedImage image, int dx, int dy, int dx2, int dy2, int sx, int sy, int sx2, int sy2, ImageObserver obs, boolean smooth) {
List<List<Vec2>> cs = new ArrayList<>();
List<Vec2> c = new ArrayList<>();
Point2D dst = new Point2D.Double();
fillTransform.transform(new Point2D.Double(dx, dy), dst);
c.add(new Vec2(dst.getX(), dst.getY()));
fillTransform.transform(new Point2D.Double(dx2, dy), dst);
c.add(new Vec2(dst.getX(), dst.getY()));
fillTransform.transform(new Point2D.Double(dx2, dy2), dst);
c.add(new Vec2(dst.getX(), dst.getY()));
fillTransform.transform(new Point2D.Double(dx, dy2), dst);
c.add(new Vec2(dst.getX(), dst.getY()));
for (int i = 0; i < c.size(); i++) {
double x = Math.round(c.get(i).x);
double y = Math.round(c.get(i).y);
c.set(i, new Vec2(x, y));
}
cs.add(c);
AffineTransform t = new AffineTransform();
if (dx2 != dx && dy2 != dy) {
double scaleX = (sx2 - sx) / (double) (dx2 - dx);
double scaleY = (sy2 - sy) / (double) (dy2 - dy);
t = AffineTransform.getScaleInstance(scaleX, scaleY);
t.preConcatenate(fillTransform);
}
/*int w = sx2 - sx;
int h = sy2 - sy;
int one = (int) (1 * unzoom);
BufferedImage imgTrans = new BufferedImage(w + 2, h + 2, BufferedImage.TYPE_INT_ARGB_PRE);
Graphics2D g = (Graphics2D) imgTrans.getGraphics();
g.setComposite(AlphaComposite.Src);
g.setColor(new Color(0,255,0,255));
g.fillRect(0, 0, w + 2, h + 2);
g.drawImage(image, 1, 1, 1 + w, 1 + h, sx,sy,sx2,sy2, null);
*/
//t.concatenate(AffineTransform.getTranslateInstance(-1, -1));
//t.preConcatenate(AffineTransform.getTranslateInstance(-1 * unzoom, -1 * unzoom));
//new Rectangle2D.Double(sx, sy, sx2 - sx, sy2 - sy)
//rz.fillContoursWithPaint(cs, windingRule, new TexturePaint(imgTrans, new Rectangle2D.Double(0,0,w+2,h+2)), t, smooth);
//new TexturePaint(image, new Rectangle2D.Double(sx, sy, sx2 - sx, sy2 - sy))
rz.fillContoursWithPaint(cs, windingRule, Color.black, t, smooth, image);
}
private void drawImage(BufferedImage image, int x, int y, ImageObserver obs, boolean smooth) {
drawImage(image, x, y, x + image.getWidth(), y + image.getHeight(), 0, 0, image.getWidth(), image.getHeight(), obs, smooth);
}
/**
* Finalizes the path.
*/
@Override
protected void finalizePath() {
if (fillPaint != null) {
if (fillPaint instanceof MultipleGradientPaint) {
fillTransform.preConcatenate(graphics.getTransform());
rz.fillContoursWithPaint(contours, Path2D.WIND_EVEN_ODD, fillPaint, fillTransform);
} else if (fillPaint instanceof TexturePaint) {
//rz.setClipContours(contours, Path2D.WIND_EVEN_ODD);
fillTransform.preConcatenate(graphics.getTransform());
if (fillRepeat) {
rz.fillContoursWithPaint(contours, Path2D.WIND_EVEN_ODD, fillPaint, fillTransform, fillSmooth, null);
} else {
drawImage(((TexturePaint) fillPaint).getImage(), 0, 0, null, fillSmooth);
}
} else {
rz.fillContoursWithPaint(contours, Path2D.WIND_EVEN_ODD, fillPaint);
}
}
if ((linePaint != null && lineStroke != null) || lineColor != null) {
Stroke stroke = lineStroke == null ? defaultStroke : lineStroke;
Shape shape = AntialiasTools.contoursToShape(contours, Path2D.WIND_EVEN_ODD, closeLine);
Shape strokedShape = stroke.createStrokedShape(shape);
List<List<Vec2>> strokeContours = AntialiasTools.shapeToContours(strokedShape, curveFlattness);
if (linePaint != null && lineStroke != null) {
if (linePaint instanceof MultipleGradientPaint) {
lineTransform.preConcatenate(graphics.getTransform());
rz.fillContoursWithPaint(strokeContours, Path2D.WIND_NON_ZERO, linePaint, lineTransform);
} else if (linePaint instanceof TexturePaint) {
lineTransform.preConcatenate(graphics.getTransform());
rz.fillContoursWithPaint(strokeContours, Path2D.WIND_NON_ZERO, linePaint, lineTransform);
} else {
rz.fillContoursWithPaint(strokeContours, Path2D.WIND_NON_ZERO, linePaint);
}
} else if (lineColor != null) {
rz.fillContoursWithPaint(strokeContours, Path2D.WIND_NON_ZERO, lineColor);
}
}
contours.clear();
lineStroke = null;
lineColor = null;
fillPaint = null;
}
@Override
public void lineStyle(double thickness, RGB color, boolean pixelHinting, String scaleMode, int startCaps, int endCaps, int joints, float miterLimit, boolean noClose) {
finalizePath();
linePaint = null;
lineTransform = null;
if (thickness == 0) {
lineColor = null;
return;
}
lineColor = color == null ? null : color.toColor();
int capStyle = BasicStroke.CAP_ROUND;
switch (startCaps) {
case LINESTYLE2.NO_CAP:
capStyle = BasicStroke.CAP_BUTT;
break;
case LINESTYLE2.ROUND_CAP:
capStyle = BasicStroke.CAP_ROUND;
break;
case LINESTYLE2.SQUARE_CAP:
capStyle = BasicStroke.CAP_SQUARE;
break;
}
int joinStyle = BasicStroke.JOIN_ROUND;
switch (joints) {
case LINESTYLE2.BEVEL_JOIN:
joinStyle = BasicStroke.JOIN_BEVEL;
break;
case LINESTYLE2.MITER_JOIN:
joinStyle = BasicStroke.JOIN_MITER;
break;
case LINESTYLE2.ROUND_JOIN:
joinStyle = BasicStroke.JOIN_ROUND;
break;
}
if (scaleStrokes) {
switch (scaleMode) {
case "VERTICAL":
thickness *= thicknessScaleY;
break;
case "HORIZONTAL":
thickness *= thicknessScaleX;
break;
case "NORMAL":
thickness *= thicknessScale;
break;
case "NONE":
break;
}
}
thickness *= unzoom / SWF.unitDivisor;
//always display minimum stroke of 1 pixel, no matter how zoomed it is
if (thickness < 1) {
thickness = 1;
}
if (joinStyle == BasicStroke.JOIN_MITER) {
lineStroke = new ExtendedBasicStroke((float) thickness, capStyle, ExtendedBasicStroke.JOIN_MITER_CLIP, miterLimit);
} else {
lineStroke = new BasicStroke((float) thickness, capStyle, joinStyle);
}
}
}

View File

@@ -0,0 +1,48 @@
package com.jpexs.decompiler.flash.exporters.shape.aa;
/**
*
* @author JPEXS
*/
public class Vec2 {
public final double x;
public final double y;
public Vec2(double x, double y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "[" + x + ", " + y + "]";
}
@Override
public int hashCode() {
int hash = 5;
hash = 71 * hash + (int) (Double.doubleToLongBits(this.x) ^ (Double.doubleToLongBits(this.x) >>> 32));
hash = 71 * hash + (int) (Double.doubleToLongBits(this.y) ^ (Double.doubleToLongBits(this.y) >>> 32));
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Vec2 other = (Vec2) obj;
if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.x)) {
return false;
}
return Double.doubleToLongBits(this.y) == Double.doubleToLongBits(other.y);
}
}

View File

@@ -145,7 +145,7 @@ public abstract class DrawableTag extends CharacterTag implements BoundedTag {
@Override
public RECT getRectWithFilters() {
RECT r = new RECT(getRect());
RECT r = new RECT(getRectWithStrokes());
Dimension filterDimension = getFilterDimensions();
r.Xmin -= filterDimension.width;
r.Xmax += filterDimension.width;

View File

@@ -219,6 +219,9 @@ public abstract class ShapeTag extends DrawableTag implements LazyObject {
r.Ymin -= maxWidth;
r.Xmax += maxWidth;
r.Ymax += maxWidth;
r.Xmin -= SWF.unitDivisor / 2;
r.Ymin -= SWF.unitDivisor / 2;
return r;
}

View File

@@ -112,7 +112,7 @@ public class SHAPE implements NeedsCharacters, Serializable {
public RECT getEdgeBounds() {
return SHAPERECORD.getBounds(shapeRecords, null, 1, true);
}
/**
* Clears cached outline.
*/
@@ -206,4 +206,8 @@ public class SHAPE implements NeedsCharacters, Serializable {
ret.shapeRecords.add(new EndShapeRecord());
return ret;
}
public int getMaxStrokeWidth(int shapeNum) {
return SHAPERECORD.getMaxStrokeWidth(shapeRecords, null, shapeNum);
}
}

View File

@@ -118,7 +118,7 @@ public class SHAPEWITHSTYLE extends SHAPE implements NeedsCharacters, Serializab
@Override
public RECT getBounds(int shapeNum) {
return SHAPERECORD.getBounds(shapeRecords, lineStyles, shapeNum, false);
}
}
/**
* Updates morph shape tag.
@@ -291,4 +291,9 @@ public class SHAPEWITHSTYLE extends SHAPE implements NeedsCharacters, Serializab
}
return result;
}
@Override
public int getMaxStrokeWidth(int shapeNum) {
return SHAPERECORD.getMaxStrokeWidth(shapeRecords, lineStyles, shapeNum);
}
}

View File

@@ -186,6 +186,13 @@ public abstract class SHAPERECORD implements Cloneable, NeedsCharacters, Seriali
}
}
}
if (min_x == Integer.MAX_VALUE) {
min_x = 0;
}
if (min_y == Integer.MAX_VALUE) {
min_y = 0;
}
return new RECT(min_x, max_x, min_y, max_y);
}
@@ -537,4 +544,33 @@ public abstract class SHAPERECORD implements Cloneable, NeedsCharacters, Seriali
}
public abstract boolean isTooLarge();
public static int getMaxStrokeWidth(List<SHAPERECORD> records, LINESTYLEARRAY baseLineStyles, int shapeNum) {
LINESTYLEARRAY lineStyles = baseLineStyles;
int ret = 0;
for (SHAPERECORD rec : records) {
if (rec instanceof StyleChangeRecord) {
StyleChangeRecord scr = (StyleChangeRecord) rec;
if (scr.stateNewStyles) {
lineStyles = scr.lineStyles;
}
if (scr.stateLineStyle) {
if (scr.lineStyle > 0) {
int width;
if (shapeNum <= 3) {
width = lineStyles.lineStyles[scr.lineStyle - 1].width;
} else {
width = lineStyles.lineStyles2[scr.lineStyle - 1].width;
}
if (width > ret) {
ret = width;
}
}
}
}
}
return ret;
}
}