diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java index d63e2a852..0d327bf95 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -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(); } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java index 3e776b55c..c235b0bfa 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java @@ -844,18 +844,22 @@ public final class Configuration { @ConfigurationDefaultBoolean(false) @ConfigurationCategory("display") + @ConfigurationRemoved public static ConfigurationItem reduceAntialiasConflationByScalingForDisplay = null; @ConfigurationDefaultInt(4) @ConfigurationCategory("display") + @ConfigurationRemoved public static ConfigurationItem reduceAntialiasConflationByScalingValueForDisplay = null; @ConfigurationDefaultBoolean(false) @ConfigurationCategory("export") + @ConfigurationRemoved public static ConfigurationItem reduceAntialiasConflationByScalingForExport = null; - @ConfigurationDefaultInt(10) + @ConfigurationDefaultInt(4) @ConfigurationCategory("export") + @ConfigurationRemoved public static ConfigurationItem reduceAntialiasConflationByScalingValueForExport = null; @ConfigurationDefaultBoolean(true) @@ -1213,6 +1217,22 @@ public final class Configuration { @ConfigurationCategory("export") public static ConfigurationItem exportFlaAs3DisableScriptLayer = null; + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("display") + public static ConfigurationItem useMsaaForDisplay = null; + + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("export") + public static ConfigurationItem useMsaaForExport = null; + + @ConfigurationDefaultInt(4) + @ConfigurationCategory("display") + public static ConfigurationItem msaaGridForDisplay = null; + + @ConfigurationDefaultInt(4) + @ConfigurationCategory("export") + public static ConfigurationItem msaaGridForExport = null; + private static Map configurationDescriptions = new LinkedHashMap<>(); private static Map 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; - } + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FrameExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FrameExporter.java index 6af244237..5b5104a93 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FrameExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FrameExporter.java @@ -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; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/MorphShapeExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/MorphShapeExporter.java index 2834e93ec..9f0f615b7 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/MorphShapeExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/MorphShapeExporter.java @@ -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; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ShapeExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ShapeExporter.java index fdb0006d2..df10574f5 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ShapeExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ShapeExporter.java @@ -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); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/BitmapExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/BitmapExporter.java index b2e77935f..1ad3fdda2 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/BitmapExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/BitmapExporter.java @@ -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); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/aa/AntialiasTools.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/aa/AntialiasTools.java new file mode 100644 index 000000000..279d8776f --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/aa/AntialiasTools.java @@ -0,0 +1,1346 @@ +package com.jpexs.decompiler.flash.exporters.shape.aa; + +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.TexturePaint; +import java.awt.geom.AffineTransform; +import java.awt.geom.FlatteningPathIterator; +import java.awt.geom.GeneralPath; +import java.awt.geom.PathIterator; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.imageio.ImageIO; +import javax.swing.JFrame; +import javax.swing.JPanel; + +/** + * + * @author JPEXS + */ +public class AntialiasTools { + + private static final int FIXED_SHIFT = 8; + private static final int FIXED_ONE = 1 << FIXED_SHIFT; + + public static Shape contoursToShape(List> contours, int windingRule, boolean close) { + GeneralPath path = new GeneralPath(windingRule); + + if (contours == null) { + return path; + } + + for (List contour : contours) { + if (contour == null || contour.isEmpty()) { + continue; + } + + Vec2 first = contour.get(0); + path.moveTo((float) first.x, (float) first.y); + + for (int i = 1; i < contour.size(); i++) { + Vec2 p = contour.get(i); + path.lineTo((float) p.x, (float) p.y); + } + + if (close) { + path.closePath(); + } + } + + return path; + } + + public static List> shapeToContours(Shape shape, double flatness) { + List> contours = new ArrayList>(); + + if (shape == null) { + return contours; + } + + PathIterator raw = shape.getPathIterator(null); + FlatteningPathIterator it = new FlatteningPathIterator(raw, flatness); + + double[] coords = new double[6]; + + List current = null; + Vec2 moveStart = null; + Vec2 last = null; + + while (!it.isDone()) { + int type = it.currentSegment(coords); + + switch (type) { + case PathIterator.SEG_MOVETO: { + if (current != null && current.size() >= 2) { + finishContour(contours, current, moveStart); + } + + current = new ArrayList(); + moveStart = new Vec2(coords[0], coords[1]); + current.add(moveStart); + last = moveStart; + break; + } + + case PathIterator.SEG_LINETO: { + if (current == null) { + current = new ArrayList(); + moveStart = new Vec2(coords[0], coords[1]); + current.add(moveStart); + last = moveStart; + } else { + Vec2 p = new Vec2(coords[0], coords[1]); + if (!samePoint(last, p, 1e-12)) { + current.add(p); + last = p; + } + } + break; + } + + case PathIterator.SEG_CLOSE: { + if (current != null) { + finishContour(contours, current, moveStart); + current = null; + moveStart = null; + last = null; + } + break; + } + + default: + break; + } + + it.next(); + } + + if (current != null && current.size() >= 2) { + finishContour(contours, current, moveStart); + } + + return contours; + } + + private static void finishContour(List> contours, List current, Vec2 moveStart) { + if (current == null || current.size() < 2) { + return; + } + + List contour = new ArrayList(current); + + if (moveStart != null && !samePoint(contour.get(contour.size() - 1), moveStart, 1e-12)) { + contour.add(moveStart); + } + + if (contour.size() >= 2 && samePoint(contour.get(0), contour.get(contour.size() - 1), 1e-12)) { + contour.remove(contour.size() - 1); + } + + if (contour.size() >= 3) { + contours.add(contour); + } + } + + public static List removeDuplicateAndCollinear(List pts, double eps) { + List a = new ArrayList(); + if (pts == null || pts.isEmpty()) { + return a; + } + + for (int i = 0; i < pts.size(); i++) { + Vec2 p = pts.get(i); + if (a.isEmpty() || !samePoint(a.get(a.size() - 1), p, eps)) { + a.add(p); + } + } + + if (a.size() > 1 && samePoint(a.get(0), a.get(a.size() - 1), eps)) { + a.remove(a.size() - 1); + } + + boolean changed = true; + while (changed && a.size() >= 3) { + changed = false; + for (int i = 0; i < a.size(); i++) { + Vec2 prev = a.get((i - 1 + a.size()) % a.size()); + Vec2 curr = a.get(i); + Vec2 next = a.get((i + 1) % a.size()); + + if (Math.abs(triangleArea2(prev, curr, next)) <= eps + && dot(curr.x - prev.x, curr.y - prev.y, next.x - curr.x, next.y - curr.y) >= 0.0) { + a.remove(i); + changed = true; + break; + } + } + } + + return a; + } + + public static boolean samePoint(Vec2 a, Vec2 b, double eps) { + return Math.abs(a.x - b.x) <= eps && Math.abs(a.y - b.y) <= eps; + } + + public static double dot(double ax, double ay, double bx, double by) { + return ax * bx + ay * by; + } + + public static double triangleArea2(Vec2 a, Vec2 b, Vec2 c) { + return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); + } + + public static List flattenCubicBezier(Vec2 p0, Vec2 p1, Vec2 p2, Vec2 p3, double tolerance) { + List out = new ArrayList(); + out.add(p0); + flattenCubicRecursive(p0, p1, p2, p3, tolerance * tolerance, out, 0); + return out; + } + + public static void flattenCubicRecursive(Vec2 p0, Vec2 p1, Vec2 p2, Vec2 p3, + double tol2, List out, int depth) { + if (depth > 20 || cubicFlatEnough(p0, p1, p2, p3, tol2)) { + out.add(p3); + return; + } + + Vec2 p01 = midpoint(p0, p1); + Vec2 p12 = midpoint(p1, p2); + Vec2 p23 = midpoint(p2, p3); + Vec2 p012 = midpoint(p01, p12); + Vec2 p123 = midpoint(p12, p23); + Vec2 p0123 = midpoint(p012, p123); + + flattenCubicRecursive(p0, p01, p012, p0123, tol2, out, depth + 1); + flattenCubicRecursive(p0123, p123, p23, p3, tol2, out, depth + 1); + } + + public static boolean cubicFlatEnough(Vec2 p0, Vec2 p1, Vec2 p2, Vec2 p3, double tol2) { + double d1 = pointLineDistanceSq(p1, p0, p3); + double d2 = pointLineDistanceSq(p2, p0, p3); + return Math.max(d1, d2) <= tol2; + } + + public static List flattenQuadraticBezier(Vec2 p0, Vec2 p1, Vec2 p2, double tolerance) { + List out = new ArrayList(); + out.add(p0); + flattenQuadraticRecursive(p0, p1, p2, tolerance * tolerance, out, 0); + return out; + } + + public static void flattenQuadraticRecursive(Vec2 p0, Vec2 p1, Vec2 p2, + double tol2, List out, int depth) { + if (depth > 20 || quadraticFlatEnough(p0, p1, p2, tol2)) { + out.add(p2); + return; + } + + Vec2 p01 = midpoint(p0, p1); + Vec2 p12 = midpoint(p1, p2); + Vec2 p012 = midpoint(p01, p12); + + flattenQuadraticRecursive(p0, p01, p012, tol2, out, depth + 1); + flattenQuadraticRecursive(p012, p12, p2, tol2, out, depth + 1); + } + + public static boolean quadraticFlatEnough(Vec2 p0, Vec2 p1, Vec2 p2, double tol2) { + double d = pointLineDistanceSq(p1, p0, p2); + return d <= tol2; + } + + public static double pointLineDistanceSq(Vec2 p, Vec2 a, Vec2 b) { + double dx = b.x - a.x; + double dy = b.y - a.y; + double len2 = dx * dx + dy * dy; + + if (len2 == 0.0) { + double ex = p.x - a.x; + double ey = p.y - a.y; + return ex * ex + ey * ey; + } + + double t = ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2; + double qx = a.x + t * dx; + double qy = a.y + t * dy; + double ex = p.x - qx; + double ey = p.y - qy; + return ex * ex + ey * ey; + } + + public static Vec2 midpoint(Vec2 a, Vec2 b) { + return new Vec2((a.x + b.x) * 0.5, (a.y + b.y) * 0.5); + } + + public static void append(List dst, List src, boolean skipFirst) { + for (int i = skipFirst ? 1 : 0; i < src.size(); i++) { + dst.add(src.get(i)); + } + } + + public static void appendReversed(List dst, List src, boolean skipFirst) { + for (int i = src.size() - 1 - (skipFirst ? 1 : 0); i >= 0; i--) { + dst.add(src.get(i)); + } + } + + public static Rectangle2D boundsOfContours(List> contours) { + if (contours == null || contours.isEmpty()) { + return new Rectangle2D.Double(); + } + + double minX = Double.POSITIVE_INFINITY; + double minY = Double.POSITIVE_INFINITY; + double maxX = Double.NEGATIVE_INFINITY; + double maxY = Double.NEGATIVE_INFINITY; + + boolean hasPoint = false; + + for (List contour : contours) { + if (contour == null) { + continue; + } + + for (Vec2 p : contour) { + if (p == null) { + continue; + } + + hasPoint = true; + + if (p.x < minX) { + minX = p.x; + } + if (p.y < minY) { + minY = p.y; + } + if (p.x > maxX) { + maxX = p.x; + } + if (p.y > maxY) { + maxY = p.y; + } + } + } + + if (!hasPoint) { + return new Rectangle2D.Double(); + } + + return new Rectangle2D.Double( + minX, + minY, + maxX - minX, + maxY - minY + ); + } + + private static boolean pointInContours(double px, double py, List> contours, int windingRule) { + for (List contour : contours) { + int n = contour.size(); + for (int i = 0; i < n; i++) { + Vec2 a = contour.get(i); + Vec2 b = contour.get((i + 1) % n); + if (pointOnSegment(px, py, a, b, 1e-9)) { + return true; + } + } + } + + if (windingRule == PathIterator.WIND_EVEN_ODD) { + int crossings = 0; + + for (List contour : contours) { + int n = contour.size(); + for (int i = 0; i < n; i++) { + Vec2 a = contour.get(i); + Vec2 b = contour.get((i + 1) % n); + + if (((a.y > py) != (b.y > py)) + && (px < (b.x - a.x) * (py - a.y) / (b.y - a.y) + a.x)) { + crossings++; + } + } + } + + return (crossings & 1) != 0; + } else { + int winding = 0; + + for (List contour : contours) { + int n = contour.size(); + for (int i = 0; i < n; i++) { + Vec2 a = contour.get(i); + Vec2 b = contour.get((i + 1) % n); + + if (a.y <= py) { + if (b.y > py && isLeft(a, b, px, py) > 0.0) { + winding++; + } + } else { + if (b.y <= py && isLeft(a, b, px, py) < 0.0) { + winding--; + } + } + } + } + + return winding != 0; + } + } + + private static long isLeft(int x0, int y0, int x1, int y1, int px, int py) { + return (long) (x1 - x0) * (py - y0) - (long) (px - x0) * (y1 - y0); + } + + private static double isLeft(Vec2 a, Vec2 b, double px, double py) { + return (b.x - a.x) * (py - a.y) - (px - a.x) * (b.y - a.y); + } + + private static boolean pointOnSegment(double px, double py, Vec2 a, Vec2 b, double eps) { + double dx = b.x - a.x; + double dy = b.y - a.y; + double len2 = dx * dx + dy * dy; + + if (len2 <= eps * eps) { + double ex = px - a.x; + double ey = py - a.y; + return ex * ex + ey * ey <= eps * eps; + } + + double cross = dx * (py - a.y) - dy * (px - a.x); + if (Math.abs(cross) > eps) { + return false; + } + + double dot = (px - a.x) * dx + (py - a.y) * dy; + if (dot < -eps) { + return false; + } + + return dot <= len2 + eps; + } + + private static int clamp(int v, int lo, int hi) { + return Math.max(lo, Math.min(hi, v)); + } + + private static int toFixed(double v) { + return (int) Math.round(v * FIXED_ONE); + } + + private static int fixedFloorToInt(int v) { + return v >> FIXED_SHIFT; + } + + private static int fixedCeilToInt(int v) { + return (v + FIXED_ONE - 1) >> FIXED_SHIFT; + } + + private static int clamp255(int v) { + return v < 0 ? 0 : (v > 255 ? 255 : v); + } + + private static float srgbToLinear(int c) { + float x = c / 255.0f; + if (x <= 0.04045) { + return x / 12.92f; + } + return (float) Math.pow((x + 0.055) / 1.055, 2.4); + } + + private static int linearToSrgb8(double x) { + x = Math.max(0.0, Math.min(1.0, x)); + + double s; + if (x <= 0.0031308) { + s = 12.92 * x; + } else { + s = 1.055 * Math.pow(x, 1.0 / 2.4) - 0.055; + } + + return clamp255((int) Math.round(s * 255.0)); + } + + @SuppressWarnings("unchecked") + private static PreparedContours prepareContours(List> contours, int windingRule, int imageHeight) { + if (contours == null || contours.isEmpty()) { + return new PreparedContours( + windingRule, + new ArrayList(), + new PreparedEdge[imageHeight][], + 0, 0, 0, 0 + ); + } + + List preparedContours = new ArrayList(); + + int globalMinX = Integer.MAX_VALUE; + int globalMinY = Integer.MAX_VALUE; + int globalMaxX = Integer.MIN_VALUE; + int globalMaxY = Integer.MIN_VALUE; + + @SuppressWarnings("unchecked") + List[] buckets = (List[]) new List[imageHeight]; + + for (List contour : contours) { + List cleaned = AntialiasTools.removeDuplicateAndCollinear(contour, 1e-9); + if (cleaned == null || cleaned.size() < 3) { + continue; + } + + int pointCount = cleaned.size(); + int[] xs = new int[pointCount]; + int[] ys = new int[pointCount]; + + int minX = Integer.MAX_VALUE; + int minY = Integer.MAX_VALUE; + int maxX = Integer.MIN_VALUE; + int maxY = Integer.MIN_VALUE; + + for (int i = 0; i < pointCount; i++) { + Vec2 p = cleaned.get(i); + int fx = toFixed(p.x); + int fy = toFixed(p.y); + xs[i] = fx; + ys[i] = fy; + if (fx < minX) { + minX = fx; + } + if (fy < minY) { + minY = fy; + } + if (fx > maxX) { + maxX = fx; + } + if (fy > maxY) { + maxY = fy; + } + } + + PreparedEdge[] edges = new PreparedEdge[pointCount]; + for (int i = 0; i < pointCount; i++) { + int x0 = xs[i]; + int y0 = ys[i]; + int x1 = xs[(i + 1) % pointCount]; + int y1 = ys[(i + 1) % pointCount]; + PreparedEdge e = new PreparedEdge(x0, y0, x1, y1); + edges[i] = e; + + int rowMin = clamp(fixedFloorToInt(e.minY), 0, imageHeight - 1); + int rowMax = clamp(fixedCeilToInt(e.maxY), 0, imageHeight - 1); + + for (int row = rowMin; row <= rowMax; row++) { + if (buckets[row] == null) { + buckets[row] = new ArrayList(); + } + buckets[row].add(e); + } + } + + preparedContours.add(new PreparedContour(xs, ys, edges, minX, minY, maxX, maxY)); + + if (minX < globalMinX) { + globalMinX = minX; + } + if (minY < globalMinY) { + globalMinY = minY; + } + if (maxX > globalMaxX) { + globalMaxX = maxX; + } + if (maxY > globalMaxY) { + globalMaxY = maxY; + } + } + + if (preparedContours.isEmpty()) { + globalMinX = globalMinY = globalMaxX = globalMaxY = 0; + } + + PreparedEdge[][] arrayBuckets = new PreparedEdge[imageHeight][]; + for (int i = 0; i < imageHeight; i++) { + if (buckets[i] != null) { + arrayBuckets[i] = buckets[i].toArray(new PreparedEdge[0]); + } + } + + return new PreparedContours( + windingRule, + preparedContours, + arrayBuckets, + globalMinX, globalMinY, globalMaxX, globalMaxY + ); + } + + private static boolean pointInPreparedContours(int pxFixed, int pyFixed, PreparedContours prepared) { + if (prepared == null || prepared.contours.isEmpty()) { + return false; + } + + if (pxFixed < prepared.minX || pxFixed > prepared.maxX || pyFixed < prepared.minY || pyFixed > prepared.maxY) { + return false; + } + + int row = clamp(fixedFloorToInt(pyFixed), 0, prepared.edgeBucketsByRow.length - 1); + PreparedEdge[] candidateEdges = prepared.edgeBucketsByRow[row]; + if (candidateEdges == null || candidateEdges.length == 0) { + return false; + } + + final int eps = 1; + final int edgeCount = candidateEdges.length; + + if (prepared.windingRule == PathIterator.WIND_EVEN_ODD) { + int crossings = 0; + + for (int i = 0; i < edgeCount; i++) { + PreparedEdge e = candidateEdges[i]; + + if (e.bboxContains(pxFixed, pyFixed, eps) && pointOnPreparedEdge(pxFixed, pyFixed, e, eps)) { + return true; + } + + if (!e.horizontal && ((e.y0 > pyFixed) != (e.y1 > pyFixed))) { + long xCross = ((long) e.x0 << FIXED_SHIFT) + ((long) (pyFixed - e.y0) * e.dxOverDyFixed); + if (((long) pxFixed << FIXED_SHIFT) < xCross) { + crossings++; + } + } + } + + return (crossings & 1) != 0; + } else { + int winding = 0; + + for (int i = 0; i < edgeCount; i++) { + PreparedEdge e = candidateEdges[i]; + + if (e.bboxContains(pxFixed, pyFixed, eps) && pointOnPreparedEdge(pxFixed, pyFixed, e, eps)) { + return true; + } + + if (!e.horizontal) { + long dpy = (long) pyFixed - e.y0; + long cross = (long) e.dx * dpy - (long) (pxFixed - e.x0) * e.dy; + if (e.y0 <= pyFixed) { + if (e.y1 > pyFixed && cross > 0L) { + winding++; + } + } else { + if (e.y1 <= pyFixed && cross < 0L) { + winding--; + } + } + } + } + + return winding != 0; + } + } + + private static boolean pointOnPreparedEdge(int pxFixed, int pyFixed, PreparedEdge e, int eps) { + if (e.len2 <= (long) eps * eps) { + long ex = (long) pxFixed - e.x0; + long ey = (long) pyFixed - e.y0; + return ex * ex + ey * ey <= (long) eps * eps; + } + + long cross = (long) e.dx * ((long) pyFixed - e.y0) - (long) e.dy * ((long) pxFixed - e.x0); + if (cross > eps || cross < -eps) { + return false; + } + + long dot = ((long) pxFixed - e.x0) * e.dx + ((long) pyFixed - e.y0) * e.dy; + if (dot < -eps) { + return false; + } + + return dot <= e.len2 + eps; + } + + public static final class SceneRasterizerMSAA { + + private final int width; + private final int height; + private final int sampleCount; + private final int[] sampleOffsets; + + private static final int SUBPIXEL_BITS = 8; + private static final int SUBPIXEL_SCALE = 1 << SUBPIXEL_BITS; + + private final float[] sampleA; + private final float[] sampleR; + private final float[] sampleG; + private final float[] sampleB; + + private PreparedContours preparedClip = null; + private int clipWindingRule = PathIterator.WIND_EVEN_ODD; + + private static final float[] SRGB_TO_LINEAR_8 = buildSrgbToLinear8(); + + private static final int LINEAR_TO_SRGB_LUT_SIZE = 4096; + private static final int[] LINEAR_TO_SRGB_8 = buildLinearToSrgb8(); + + private static int[] buildLinearToSrgb8() { + int[] t = new int[LINEAR_TO_SRGB_LUT_SIZE + 1]; + for (int i = 0; i <= LINEAR_TO_SRGB_LUT_SIZE; i++) { + double x = i / (double) LINEAR_TO_SRGB_LUT_SIZE; + double s; + if (x <= 0.0031308) { + s = 12.92 * x; + } else { + s = 1.055 * Math.pow(x, 1.0 / 2.4) - 0.055; + } + int v = (int) Math.round(s * 255.0); + if (v < 0) { + v = 0; + } + if (v > 255) { + v = 255; + } + t[i] = v; + } + return t; + } + + private static int linearToSrgb8Fast(float x) { + if (x <= 0f) { + return 0; + } + if (x >= 1f) { + return 255; + } + int idx = (int) (x * LINEAR_TO_SRGB_LUT_SIZE + 0.5f); + if (idx < 0) { + idx = 0; + } + if (idx > LINEAR_TO_SRGB_LUT_SIZE) { + idx = LINEAR_TO_SRGB_LUT_SIZE; + } + return LINEAR_TO_SRGB_8[idx]; + } + + private static float[] buildSrgbToLinear8() { + float[] t = new float[256]; + for (int i = 0; i < 256; i++) { + double x = i / 255.0; + if (x <= 0.04045) { + t[i] = (float) (x / 12.92); + } else { + t[i] = (float) Math.pow((x + 0.055) / 1.055, 2.4); + } + } + return t; + } + + public SceneRasterizerMSAA(int width, int height, int sampleGrid) { + this.width = width; + this.height = height; + this.sampleCount = sampleGrid * sampleGrid; + this.sampleOffsets = buildRegularSamplePattern(sampleGrid); + + int totalSamples = width * height * sampleCount; + this.sampleA = new float[totalSamples]; + this.sampleR = new float[totalSamples]; + this.sampleG = new float[totalSamples]; + this.sampleB = new float[totalSamples]; + } + + public void clear(int argb) { + int a8 = (argb >>> 24) & 0xFF; + int r8 = (argb >>> 16) & 0xFF; + int g8 = (argb >>> 8) & 0xFF; + int b8 = argb & 0xFF; + + float a = a8 / 255.0f; + float r = (float) srgbToLinear(r8) * a; + float g = (float) srgbToLinear(g8) * a; + float b = (float) srgbToLinear(b8) * a; + + for (int i = 0; i < sampleA.length; i++) { + sampleA[i] = a; + sampleR[i] = r; + sampleG[i] = g; + sampleB[i] = b; + } + } + + public void setClipContours(List> contours, int windingRule) { + if (contours == null || contours.isEmpty()) { + this.preparedClip = null; + return; + } + this.preparedClip = prepareContours(contours, windingRule, height); + } + + public void clearClip() { + this.preparedClip = null; + } + + public void setClipContour(List contour) { + if (contour == null) { + clearClip(); + return; + } + + List> contours = new ArrayList>(); + contours.add(contour); + setClipContours(contours, PathIterator.WIND_EVEN_ODD); + } + + private boolean passesClip(int sxFixed, int syFixed) { + if (preparedClip == null) { + return true; + } + return pointInPreparedContours(sxFixed, syFixed, preparedClip); + } + + public void fillContoursWithPaint(List> contours, int windingRule, Paint paint) { + fillContoursWithPaint(contours, windingRule, paint, null); + } + + public void fillContoursWithPaint(List> contours, + int windingRule, + Paint paint, + AffineTransform paintTransform) { + fillContoursWithPaint(contours, windingRule, paint, paintTransform, false, null); + } + + public void fillContoursWithPaint(List> contours, int windingRule, Paint paint, AffineTransform paintTransform, boolean smooth, BufferedImage imagePaint) { + if (contours == null || contours.isEmpty() || paint == null) { + return; + } + + PreparedContours prepared = prepareContours(contours, windingRule, height); + BufferedImage paintImage = createPaintImage(paint, paintTransform, smooth, imagePaint); + fillPreparedContoursWithPaintImage(prepared, paintImage); + } + + private int[] buildRegularSamplePattern(int grid) { + int[] arr = new int[grid * grid * 2]; + int idx = 0; + + for (int y = 0; y < grid; y++) { + for (int x = 0; x < grid; x++) { + arr[idx++] = ((2 * x + 1) * SUBPIXEL_SCALE) / (2 * grid); + arr[idx++] = ((2 * y + 1) * SUBPIXEL_SCALE) / (2 * grid); + } + } + return arr; + } + + private void fillPreparedContoursWithPaintImage(PreparedContours prepared, BufferedImage paintImage) { + if (prepared == null || prepared.contours.isEmpty()) { + return; + } + + int minX = clamp(fixedFloorToInt(prepared.minX), 0, width); + int minY = clamp(fixedFloorToInt(prepared.minY), 0, height); + int maxX = clamp(fixedCeilToInt(prepared.maxX), 0, width); + int maxY = clamp(fixedCeilToInt(prepared.maxY), 0, height); + + for (int py = minY; py < maxY; py++) { + for (int px = minX; px < maxX; px++) { + int base = (py * width + px) * sampleCount; + + int pxFixedBase = px << FIXED_SHIFT; + int pyFixedBase = py << FIXED_SHIFT; + for (int s = 0; s < sampleCount; s++) { + int sxFixed = pxFixedBase + sampleOffsets[2 * s]; + int syFixed = pyFixedBase + sampleOffsets[2 * s + 1]; + + if (pointInPreparedContours(sxFixed, syFixed, prepared) && passesClip(sxFixed, syFixed)) { + int sampleX = clamp(fixedFloorToInt(sxFixed), 0, width - 1); + int sampleY = clamp(fixedFloorToInt(syFixed), 0, height - 1); + int argb = paintImage.getRGB(sampleX, sampleY); + + int a8 = (argb >>> 24) & 0xFF; + int r8 = (argb >>> 16) & 0xFF; + int g8 = (argb >>> 8) & 0xFF; + int b8 = argb & 0xFF; + + float srcA = a8 / 255.0f; + if (srcA <= 0.0f) { + continue; + } + + float srcR = (float) SRGB_TO_LINEAR_8[r8] * srcA; + float srcG = (float) SRGB_TO_LINEAR_8[g8] * srcA; + float srcB = (float) SRGB_TO_LINEAR_8[b8] * srcA; + + int idx = base + s; + float dstA = sampleA[idx]; + float invSrcA = 1.0f - srcA; + + sampleR[idx] = srcR + sampleR[idx] * invSrcA; + sampleG[idx] = srcG + sampleG[idx] * invSrcA; + sampleB[idx] = srcB + sampleB[idx] * invSrcA; + sampleA[idx] = srcA + dstA * invSrcA; + } + } + } + } + } + + private void fillContoursWithPaintImage(List> contours, + int windingRule, + BufferedImage paintImage) { + double minXf = Double.POSITIVE_INFINITY; + double minYf = Double.POSITIVE_INFINITY; + double maxXf = Double.NEGATIVE_INFINITY; + double maxYf = Double.NEGATIVE_INFINITY; + + for (List contour : contours) { + for (Vec2 p : contour) { + if (p.x < minXf) { + minXf = p.x; + } + if (p.y < minYf) { + minYf = p.y; + } + if (p.x > maxXf) { + maxXf = p.x; + } + if (p.y > maxYf) { + maxYf = p.y; + } + } + } + + if (!(minXf <= maxXf) || !(minYf <= maxYf)) { + return; + } + + int minX = clamp((int) Math.floor(minXf), 0, width); + int minY = clamp((int) Math.floor(minYf), 0, height); + int maxX = clamp((int) Math.ceil(maxXf), 0, width); + int maxY = clamp((int) Math.ceil(maxYf), 0, height); + + for (int py = minY; py < maxY; py++) { + for (int px = minX; px < maxX; px++) { + int base = (py * width + px) * sampleCount; + + for (int s = 0; s < sampleCount; s++) { + double sx = px + (sampleOffsets[2 * s] / (double) SUBPIXEL_SCALE); + double sy = py + (sampleOffsets[2 * s + 1] / (double) SUBPIXEL_SCALE); + + if (pointInContours(sx, sy, contours, windingRule) && passesClip(toFixed(sx), toFixed(sy))) { + int sampleX = clamp((int) Math.floor(sx), 0, width - 1); + int sampleY = clamp((int) Math.floor(sy), 0, height - 1); + int argb = paintImage.getRGB(sampleX, sampleY); + + int a8 = (argb >>> 24) & 0xFF; + int r8 = (argb >>> 16) & 0xFF; + int g8 = (argb >>> 8) & 0xFF; + int b8 = argb & 0xFF; + + float srcA = a8 / 255.0f; + if (srcA <= 0.0f) { + continue; + } + + float srcR = (float) srgbToLinear(r8) * srcA; + float srcG = (float) srgbToLinear(g8) * srcA; + float srcB = (float) srgbToLinear(b8) * srcA; + + int idx = base + s; + float dstA = sampleA[idx]; + float invSrcA = 1.0f - srcA; + + sampleR[idx] = srcR + sampleR[idx] * invSrcA; + sampleG[idx] = srcG + sampleG[idx] * invSrcA; + sampleB[idx] = srcB + sampleB[idx] * invSrcA; + sampleA[idx] = srcA + dstA * invSrcA; + } + } + } + } + } + + private BufferedImage createPaintImage(Paint paint, AffineTransform paintTransform, boolean smooth, BufferedImage textureImage) { + BufferedImage paintImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = paintImage.createGraphics(); + try { + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); + + if (paint instanceof TexturePaint || textureImage != null) { + if (smooth) { + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + } else { + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); + } + } + + if (paintTransform == null) { + paintTransform = new AffineTransform(); + } + + g.setTransform(paintTransform); + + g.setPaint(paint); + + if (textureImage != null) { + g.drawImage(textureImage, 0, 0, null); + } else { + Matrix m = new Matrix(paintTransform); + + m = m.inverse(); + Rectangle2D r = m.transform(new Rectangle2D.Double(-width * 2, -height * 2, width * 4, height * 4)); + g.fillRect((int) r.getX(), (int) r.getY(), (int) r.getWidth(), (int) r.getHeight()); + } + } finally { + g.dispose(); + } + return paintImage; + } + + public void resolveTo(BufferedImage img) { + int[] dst = ((java.awt.image.DataBufferInt) img.getRaster().getDataBuffer()).getData(); + final boolean dstIsPremultiplied = img.isAlphaPremultiplied(); + + int pixelIndex = 0; + int sampleBase = 0; + + for (int py = 0; py < height; py++) { + for (int px = 0; px < width; px++, pixelIndex++, sampleBase += sampleCount) { + float sumA = 0f; + float sumPR = 0f; + float sumPG = 0f; + float sumPB = 0f; + + for (int s = 0; s < sampleCount; s++) { + int idx = sampleBase + s; + sumA += sampleA[idx]; + sumPR += sampleR[idx]; + sumPG += sampleG[idx]; + sumPB += sampleB[idx]; + } + + // source z MSAA bufferu: linear premultiplied + float srcA = sumA / sampleCount; + float srcPR = sumPR / sampleCount; + float srcPG = sumPG / sampleCount; + float srcPB = sumPB / sampleCount; + + // destination pixel + int dstArgb = dst[pixelIndex]; + int dstA8 = (dstArgb >>> 24) & 0xFF; + int dstR8 = (dstArgb >>> 16) & 0xFF; + int dstG8 = (dstArgb >>> 8) & 0xFF; + int dstB8 = dstArgb & 0xFF; + + float dstA = dstA8 / 255.0f; + float dstPR = 0f; + float dstPG = 0f; + float dstPB = 0f; + + if (dstA > 1e-12f) { + if (dstIsPremultiplied) { + // ARGB_PRE: uložené RGB je sRGB-premultiplied + float dstRs = (dstR8 / 255.0f) / dstA; + float dstGs = (dstG8 / 255.0f) / dstA; + float dstBs = (dstB8 / 255.0f) / dstA; + + dstRs = clamp01(dstRs); + dstGs = clamp01(dstGs); + dstBs = clamp01(dstBs); + + float dstRl = srgbToLinearFloat(dstRs); + float dstGl = srgbToLinearFloat(dstGs); + float dstBl = srgbToLinearFloat(dstBs); + + dstPR = dstRl * dstA; + dstPG = dstGl * dstA; + dstPB = dstBl * dstA; + } else { + // ARGB: uložené RGB je straight sRGB + float dstRl = srgb8ToLinearFloat(dstR8); + float dstGl = srgb8ToLinearFloat(dstG8); + float dstBl = srgb8ToLinearFloat(dstB8); + + dstPR = dstRl * dstA; + dstPG = dstGl * dstA; + dstPB = dstBl * dstA; + } + } + + // SrcOver v linear premultiplied + float invSrcA = 1.0f - srcA; + + float outA = srcA + dstA * invSrcA; + float outPR = srcPR + dstPR * invSrcA; + float outPG = srcPG + dstPG * invSrcA; + float outPB = srcPB + dstPB * invSrcA; + + int outA8 = clamp255((int) (outA * 255.0f + 0.5f)); + int outR8; + int outG8; + int outB8; + + if (outA > 1e-12f) { + float outRl = outPR / outA; + float outGl = outPG / outA; + float outBl = outPB / outA; + + outRl = clamp01(outRl); + outGl = clamp01(outGl); + outBl = clamp01(outBl); + + if (dstIsPremultiplied) { + // zapisujeme do ARGB_PRE: + // linear premult -> straight linear -> straight sRGB -> premult v sRGB + float outRs = linearToSrgbFloat(outRl); + float outGs = linearToSrgbFloat(outGl); + float outBs = linearToSrgbFloat(outBl); + + outR8 = clamp255((int) (outRs * outA * 255.0f + 0.5f)); + outG8 = clamp255((int) (outGs * outA * 255.0f + 0.5f)); + outB8 = clamp255((int) (outBs * outA * 255.0f + 0.5f)); + } else { + // zapisujeme do straight ARGB + outR8 = linearToSrgb8Fast(outRl); + outG8 = linearToSrgb8Fast(outGl); + outB8 = linearToSrgb8Fast(outBl); + } + } else { + outR8 = outG8 = outB8 = 0; + } + + dst[pixelIndex] = (outA8 << 24) | (outR8 << 16) | (outG8 << 8) | outB8; + } + } + } + + private static float clamp01(float v) { + if (v < 0f) { + return 0f; + } + if (v > 1f) { + return 1f; + } + return v; + } + + private static float srgb8ToLinearFloat(int c8) { + return SRGB_TO_LINEAR_8[c8]; + } + + private static float srgbToLinearFloat(float s) { + s = clamp01(s); + if (s <= 0.04045f) { + return s / 12.92f; + } + return (float) Math.pow((s + 0.055f) / 1.055f, 2.4f); + } + + private static float linearToSrgbFloat(float l) { + l = clamp01(l); + if (l <= 0.0031308f) { + return 12.92f * l; + } + return (float) (1.055 * Math.pow(l, 1.0 / 2.4) - 0.055); + } + + public void resolveToReplace(BufferedImage img) { + int[] dst = ((java.awt.image.DataBufferInt) img.getRaster().getDataBuffer()).getData(); + final boolean dstIsPremultiplied = img.isAlphaPremultiplied(); + + int pixelIndex = 0; + int sampleBase = 0; + + for (int py = 0; py < height; py++) { + for (int px = 0; px < width; px++, pixelIndex++, sampleBase += sampleCount) { + float sumA = 0f; + float sumPR = 0f; + float sumPG = 0f; + float sumPB = 0f; + + for (int s = 0; s < sampleCount; s++) { + int idx = sampleBase + s; + sumA += sampleA[idx]; + sumPR += sampleR[idx]; + sumPG += sampleG[idx]; + sumPB += sampleB[idx]; + } + + // source z MSAA bufferu: linear premultiplied + float outA = sumA / sampleCount; + float outPR = sumPR / sampleCount; + float outPG = sumPG / sampleCount; + float outPB = sumPB / sampleCount; + + int outA8 = clamp255((int) (outA * 255.0f + 0.5f)); + int outR8; + int outG8; + int outB8; + + if (outA > 1e-12f) { + // straight linear + float outRl = outPR / outA; + float outGl = outPG / outA; + float outBl = outPB / outA; + + outRl = clamp01(outRl); + outGl = clamp01(outGl); + outBl = clamp01(outBl); + + if (dstIsPremultiplied) { + // zapisujeme do ARGB_PRE: + // linear premult -> straight linear -> straight sRGB -> premult v sRGB + float outRs = linearToSrgbFloat(outRl); + float outGs = linearToSrgbFloat(outGl); + float outBs = linearToSrgbFloat(outBl); + + outR8 = clamp255((int) (outRs * outA * 255.0f + 0.5f)); + outG8 = clamp255((int) (outGs * outA * 255.0f + 0.5f)); + outB8 = clamp255((int) (outBs * outA * 255.0f + 0.5f)); + } else { + // zapisujeme do straight ARGB + outR8 = linearToSrgb8Fast(outRl); + outG8 = linearToSrgb8Fast(outGl); + outB8 = linearToSrgb8Fast(outBl); + } + } else { + outR8 = outG8 = outB8 = 0; + } + + dst[pixelIndex] = (outA8 << 24) | (outR8 << 16) | (outG8 << 8) | outB8; + } + } + } + } + + static final class PreparedContours { + + final int windingRule; + final List contours; + final PreparedEdge[][] edgeBucketsByRow; + final int minX; + final int minY; + final int maxX; + final int maxY; + + PreparedContours(int windingRule, + List contours, + PreparedEdge[][] edgeBucketsByRow, + int minX, int minY, int maxX, int maxY) { + this.windingRule = windingRule; + this.contours = contours; + this.edgeBucketsByRow = edgeBucketsByRow; + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + } + + static final class PreparedContour { + + final int[] xs; + final int[] ys; + final PreparedEdge[] edges; + final int minX; + final int minY; + final int maxX; + final int maxY; + + PreparedContour(int[] xs, + int[] ys, + PreparedEdge[] edges, + int minX, int minY, int maxX, int maxY) { + this.xs = xs; + this.ys = ys; + this.edges = edges; + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + boolean bboxContains(int x, int y) { + return x >= minX && x <= maxX && y >= minY && y <= maxY; + } + } + + static final class PreparedEdge { + + final int x0; + final int y0; + final int x1; + final int y1; + final int minX; + final int minY; + final int maxX; + final int maxY; + final int dx; + final int dy; + final int dxOverDyFixed; + final long len2; + final boolean horizontal; + + PreparedEdge(int x0, int y0, int x1, int y1) { + this.x0 = x0; + this.y0 = y0; + this.x1 = x1; + this.y1 = y1; + this.minX = Math.min(x0, x1); + this.minY = Math.min(y0, y1); + this.maxX = Math.max(x0, x1); + this.maxY = Math.max(y0, y1); + this.dx = x1 - x0; + this.dy = y1 - y0; + this.len2 = (long) dx * dx + (long) dy * dy; + this.horizontal = (dy == 0); + this.dxOverDyFixed = horizontal ? 0 : (int) Math.round((double) dx * FIXED_ONE / (double) dy); + } + + boolean bboxContains(int x, int y, int eps) { + return x >= minX - eps && x <= maxX + eps && y >= minY - eps && y <= maxY + eps; + } + } + + public static void main(String[] args) { + System.setProperty("sun.java2d.uiScale", "1.0"); + JFrame f = new JFrame(); + f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + f.setSize(800, 800); + BufferedImage img = null; + try { + img = ImageIO.read(new File("c:\\FlashRelated\\ten.png")); + } catch (IOException ex) { + Logger.getLogger(AntialiasTools.class.getName()).log(Level.SEVERE, null, ex); + } + final BufferedImage fimg = img; + JPanel p = new JPanel() { + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g; + /*AffineTransform t = AffineTransform.getScaleInstance(16, 16); + t.preConcatenate(AffineTransform.getTranslateInstance(100, 100));*/ + g2.translate(100, 100); + g2.scale(16, 16); + //g2.setTransform(t); + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g2.setPaint(new TexturePaint(fimg, new Rectangle2D.Double(0, 0, 10, 10))); + //g2.fillRect(0,0,10,10); + double eps = 0; //0.5; //0.5 / 16.0; // půl cílového pixelu v "user space" + g2.fill(new Rectangle2D.Double(0, 0, 10 - eps, 10 - eps)); + } + + }; + f.setContentPane(p); + f.setVisible(true); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/aa/AntialiasedBitmapExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/aa/AntialiasedBitmapExporter.java new file mode 100644 index 000000000..d249f7659 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/aa/AntialiasedBitmapExporter.java @@ -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> 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> 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 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> cs = new ArrayList<>(); + List 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> 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); + } + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/aa/Vec2.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/aa/Vec2.java new file mode 100644 index 000000000..e6097a09d --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/aa/Vec2.java @@ -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); + } + + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/DrawableTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/DrawableTag.java index 1e23b4261..2a941df68 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/DrawableTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/DrawableTag.java @@ -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; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ShapeTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ShapeTag.java index 3890ee752..4f2e59389 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ShapeTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ShapeTag.java @@ -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; } 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 bdceae476..1079d0402 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 @@ -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); + } } 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 5f4a44fdb..b88c51d0b 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 @@ -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); + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/SHAPERECORD.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/SHAPERECORD.java index 7ce48a4e5..c2123c020 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/SHAPERECORD.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/shaperecords/SHAPERECORD.java @@ -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 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; + } } diff --git a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java index 57ae2c01e..092ad2db0 100644 --- a/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java +++ b/src/com/jpexs/decompiler/flash/console/CommandLineArgumentParser.java @@ -2332,7 +2332,7 @@ public class CommandLineArgumentParser { } } - int aaScale = Configuration.reduceAntialiasConflationByScalingForExport.get() ? Configuration.reduceAntialiasConflationByScalingValueForExport.get() : 1; + int aaScale = Configuration.useMsaaForExport.get() ? Configuration.msaaGridForExport.get() : 1; // Here the exportFormats array should contain only validitems commandLineMode = true; @@ -3014,7 +3014,7 @@ public class CommandLineArgumentParser { File outFile = new File(args.pop()); printHeader(); - int aaScale = Configuration.reduceAntialiasConflationByScalingForExport.get() ? Configuration.reduceAntialiasConflationByScalingValueForExport.get() : 1; + int aaScale = Configuration.useMsaaForExport.get() ? Configuration.msaaGridForExport.get() : 1; try (StdInAwareFileInputStream is = new StdInAwareFileInputStream(inFile)) { diff --git a/src/com/jpexs/decompiler/flash/gui/ExportDialog.java b/src/com/jpexs/decompiler/flash/gui/ExportDialog.java index 48192c474..3641d6c3b 100644 --- a/src/com/jpexs/decompiler/flash/gui/ExportDialog.java +++ b/src/com/jpexs/decompiler/flash/gui/ExportDialog.java @@ -174,6 +174,8 @@ public class ExportDialog extends AppDialog { private JCheckBox transparentFrameBackgroundCheckBox; + private JCheckBox antialiasCheckBox; + private JTextField durationTextField = new JTextField(4); private JTextField numberOfFramesTextField = new JTextField(4); @@ -276,6 +278,8 @@ public class ExportDialog extends AppDialog { } } + Configuration.useMsaaForExport.set(antialiasCheckBox.isSelected()); + StringBuilder cfg = new StringBuilder(); for (int i = 0; i < optionNames.length; i++) { Object val = ((ComboValue) combos[i].getSelectedItem()).value; @@ -347,6 +351,70 @@ public class ExportDialog extends AppDialog { numberOfFramesTextField.setVisible(mode.hasFrames()); } + boolean aaVisible = false; + + if (isOptionEnabled(FrameExportMode.class)) { + FrameExportMode mode = getValue(FrameExportMode.class); + switch (mode) { + case CANVAS: + case PDF: + case SWF: + case SVG: + break; + default: + aaVisible = true; + } + } + if (isOptionEnabled(SpriteExportMode.class)) { + SpriteExportMode mode = getValue(SpriteExportMode.class); + switch (mode) { + case CANVAS: + case PDF: + case SWF: + case SVG: + break; + default: + aaVisible = true; + } + } + if (isOptionEnabled(ButtonExportMode.class)) { + ButtonExportMode mode = getValue(ButtonExportMode.class); + switch (mode) { + case SWF: + case SVG: + case SVG_COMBINED: + break; + default: + aaVisible = true; + } + } + if (isOptionEnabled(ShapeExportMode.class)) { + ShapeExportMode mode = getValue(ShapeExportMode.class); + switch (mode) { + case CANVAS: + case SWF: + case SVG: + break; + default: + aaVisible = true; + } + } + if (isOptionEnabled(MorphShapeExportMode.class)) { + MorphShapeExportMode mode = getValue(MorphShapeExportMode.class); + switch (mode) { + case CANVAS: + case SWF: + case SVG: + case SVG_FRAMES: + case SVG_START_END: + break; + default: + aaVisible = true; + } + } + + antialiasCheckBox.setVisible(aaVisible); + transparentFrameBackgroundCheckBox.setVisible(isOptionEnabled(FrameExportMode.class)); boolean hasZoom = false; @@ -521,14 +589,13 @@ public class ExportDialog extends AppDialog { label.setLabelFor(combos[i]); } - gbc.gridy++; gbc.gridx = 0; gbc.gridwidth = 5; gbc.fill = GridBagConstraints.BOTH; - + comboPanel.add(new JPanel(), gbc); - + gbc.insets = new Insets(2, 2, 2, 2); embedCheckBox = new JCheckBox(translate("embed")); @@ -571,6 +638,24 @@ public class ExportDialog extends AppDialog { } } + antialiasCheckBox = new JCheckBox(translate("antialias")); + antialiasCheckBox.setToolTipText(translate("antialias.hint")); + antialiasCheckBox.setVisible(false); + + if (Configuration.useMsaaForExport.get()) { + antialiasCheckBox.setSelected(true); + } + + if (visibleOptionClasses.contains(FrameExportMode.class) + || visibleOptionClasses.contains(ButtonExportMode.class) + || visibleOptionClasses.contains(ShapeExportMode.class) + || visibleOptionClasses.contains(MorphShapeExportMode.class) + || visibleOptionClasses.contains(SpriteExportMode.class)) { + gbc.gridy++; + antialiasCheckBox.setVisible(true); + comboPanel.add(antialiasCheckBox, gbc); + } + durationTextField.setVisible(false); numberOfFramesTextField.setVisible(false); if (visibleOptionClasses.contains(MorphShapeExportMode.class)) { diff --git a/src/com/jpexs/decompiler/flash/gui/FolderPreviewPanel.java b/src/com/jpexs/decompiler/flash/gui/FolderPreviewPanel.java index 14d822acd..7aa4b8d18 100644 --- a/src/com/jpexs/decompiler/flash/gui/FolderPreviewPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/FolderPreviewPanel.java @@ -308,7 +308,7 @@ public class FolderPreviewPanel extends JPanel { treeItem = ((SceneFrame) treeItem).getFrame(); } - int aaScale = Configuration.reduceAntialiasConflationByScalingForDisplay.get() ? Configuration.reduceAntialiasConflationByScalingValueForDisplay.get() : 1; + int aaScale = Configuration.useMsaaForDisplay.get() ? Configuration.msaaGridForDisplay.get() : 1; if (treeItem instanceof Frame) { Frame fn = (Frame) treeItem; @@ -333,8 +333,6 @@ public class FolderPreviewPanel extends JPanel { String key = "frame_" + fn.frame + "_" + timeline.id + "_" + zoom; imgSrc = swf.getFromCache(key); if (imgSrc == null) { - aaScale = Configuration.calculateRealAaScale(rect.getWidth(), rect.getHeight(), zoom, aaScale); - imgSrc = SWF.frameToImageGet(timeline, fn.frame, 0, null, 0, rect, new Matrix(), null, swf.getBackgroundColor() == null ? null : swf.getBackgroundColor().backgroundColor.toColor(), zoom, !Configuration.disableBitmapSmoothing.get(), aaScale); swf.putToCache(key, imgSrc); } @@ -385,23 +383,14 @@ public class FolderPreviewPanel extends JPanel { return null; } - aaScale = Configuration.calculateRealAaScale(width, height, 1, aaScale); - if (imgSrc == null) { - m = m.preConcatenate(Matrix.getScaleInstance(scale * aaScale)); + m = m.preConcatenate(Matrix.getScaleInstance(scale)); DrawableTag drawable = (DrawableTag) treeItem; ExportRectangle viewRectangle = new ExportRectangle(0, 0, ow, oh); - SerializableImage imageLarger = new SerializableImage(width * aaScale, height * aaScale, SerializableImage.TYPE_INT_ARGB); + SerializableImage imageLarger = new SerializableImage(width, height, SerializableImage.TYPE_INT_ARGB); imageLarger.fillTransparent(); - drawable.toImage(0, 0, 0, new RenderContext(), imageLarger, imageLarger, false, m, new Matrix(), m, m, null, scale * aaScale, false, viewRectangle, viewRectangle, true, Timeline.DRAW_MODE_ALL, 0, !Configuration.disableBitmapSmoothing.get(), aaScale); - if (aaScale > 1) { - SerializableImage imageNormalSize = new SerializableImage(width, height, SerializableImage.TYPE_INT_ARGB); - imageNormalSize.fillTransparent(); - - imageNormalSize.getGraphics().drawImage(imageLarger.getBufferedImage().getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null); - return imageNormalSize; - } + drawable.toImage(0, 0, 0, new RenderContext(), imageLarger, imageLarger, false, m, new Matrix(), m, m, null, scale, false, viewRectangle, viewRectangle, true, Timeline.DRAW_MODE_ALL, 0, !Configuration.disableBitmapSmoothing.get(), aaScale); return imageLarger; } diff --git a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java index 8f56f766f..8aaecbe08 100644 --- a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java @@ -5081,7 +5081,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { Matrix m = Matrix.getTranslateInstance(-rect.Xmin * zoomDouble, -rect.Ymin * zoomDouble); m.scale(zoomDouble); - int aaScale = Configuration.reduceAntialiasConflationByScalingForDisplay.get() ? Configuration.reduceAntialiasConflationByScalingValueForDisplay.get() : 1; + int aaScale = Configuration.useMsaaForDisplay.get() ? Configuration.msaaGridForDisplay.get() : 1; textTag.toImage(0, 0, 0, new RenderContext(), image, image, false, m, m, m, m, new ConstantColorColorTransform(0xFFC0C0C0), zoomDouble, false, new ExportRectangle(rect), new ExportRectangle(rect), true, Timeline.DRAW_MODE_ALL, 0, false, aaScale); @@ -5217,12 +5217,10 @@ public final class ImagePanel extends JPanel implements MediaDisplay { ) { Timeline timeline = drawable.getTimeline(); SerializableImage img; - int aaScale = Configuration.reduceAntialiasConflationByScalingForDisplay.get() ? Configuration.reduceAntialiasConflationByScalingValueForDisplay.get() : 1; + int aaScale = Configuration.useMsaaForDisplay.get() ? Configuration.msaaGridForDisplay.get() : 1; - aaScale = Configuration.calculateRealAaScale((int) viewRect.getWidth(), (int) viewRect.getHeight(), zoom, aaScale); - - int width = aaScale * (int) (viewRect.getWidth() * zoom); - int height = aaScale * (int) (viewRect.getHeight() * zoom); + int width = (int) (viewRect.getWidth() * zoom); + int height = (int) (viewRect.getHeight() * zoom); if (width == 0) { width = 1; } @@ -5231,18 +5229,14 @@ public final class ImagePanel extends JPanel implements MediaDisplay { } Rectangle realRectAA = new Rectangle(realRect); - realRectAA.x *= aaScale; - realRectAA.y *= aaScale; - realRectAA.width *= aaScale; - realRectAA.height *= aaScale; - + SerializableImage image = new SerializableImage((int) Math.ceil(width / SWF.unitDivisor), (int) Math.ceil(height / SWF.unitDivisor), SerializableImage.TYPE_INT_ARGB); image.fillTransparent(); Matrix m = new Matrix(); - m.translate(-viewRect.xMin * zoom * aaScale, -viewRect.yMin * zoom * aaScale); - m.scale(zoom * aaScale); + m.translate(-viewRect.xMin * zoom, -viewRect.yMin * zoom); + m.scale(zoom); Matrix fullM = m.clone(); @@ -5279,7 +5273,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { if (Configuration.showGrid.get() && (drawable instanceof SWF) && !Configuration.gridOverObjects.get()) { Graphics2D g = (Graphics2D) image.getBufferedImage().getGraphics(); - drawGridSwf(g, realRectAA, zoom * aaScale); + drawGridSwf(g, realRectAA, zoom); } parentMatrix = new Matrix(); @@ -5291,7 +5285,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { ignoreDepths.add(parentDepthState.depth); if (Configuration.halfTransparentParentLayersEasy.get()) { parentTimelined.getTimeline().toImage(parentFrames.get(i), 0, new RenderContext(), image, image, false, - parentMatrix.preConcatenate(m), new Matrix(), parentMatrix.preConcatenate(m), null, zoom * aaScale, true, viewRect, viewRect, parentMatrix.preConcatenate(m), true, Timeline.DRAW_MODE_ALL, 0, !Configuration.disableBitmapSmoothing.get(), + parentMatrix.preConcatenate(m), new Matrix(), parentMatrix.preConcatenate(m), null, zoom, true, viewRect, viewRect, parentMatrix.preConcatenate(m), true, Timeline.DRAW_MODE_ALL, 0, !Configuration.disableBitmapSmoothing.get(), ignoreDepths, aaScale); } parentMatrix = parentMatrix.concatenate(new Matrix(parentDepthState.matrix)); @@ -5304,12 +5298,9 @@ public final class ImagePanel extends JPanel implements MediaDisplay { g.fillRect(realRectAA.x, realRectAA.y, realRectAA.width, realRectAA.height); } - timeline.toImage(frame, time, renderContext, image, image, false, parentMatrix.preConcatenate(m), new Matrix(), parentMatrix.preConcatenate(m), null, zoom * aaScale, true, viewRect, viewRect, parentMatrix.preConcatenate(m), true, Timeline.DRAW_MODE_ALL, 0, !Configuration.disableBitmapSmoothing.get(), ignoreDepths, aaScale); - - if (Configuration.reduceAntialiasConflationByScalingForDisplay.get()) { - image = new SerializableImage(ImageResizer.resizeImage(image.getBufferedImage(), image.getWidth() / aaScale, image.getHeight() / aaScale, RenderingHints.VALUE_INTERPOLATION_BICUBIC, true)); - } + timeline.toImage(frame, time, renderContext, image, image, false, parentMatrix.preConcatenate(m), new Matrix(), parentMatrix.preConcatenate(m), null, zoom, true, viewRect, viewRect, parentMatrix.preConcatenate(m), true, Timeline.DRAW_MODE_ALL, 0, !Configuration.disableBitmapSmoothing.get(), ignoreDepths, aaScale); + Graphics2D gg = (Graphics2D) image.getGraphics(); gg.setStroke(new BasicStroke(3)); gg.setPaint(Color.green); @@ -5885,12 +5876,14 @@ public final class ImagePanel extends JPanel implements MediaDisplay { first = false; CharacterTag c = ds.getCharacter(); - ret.append(tagNameResolver.getTagName(c)); - if (ds.depth > -1) { - ret.append(" "); - ret.append(AppStrings.translate("imagePanel.depth")); - ret.append(" "); - ret.append(ds.depth); + if (c != null) { + ret.append(tagNameResolver.getTagName(c)); + if (ds.depth > -1) { + ret.append(" "); + ret.append(AppStrings.translate("imagePanel.depth")); + ret.append(" "); + ret.append(ds.depth); + } } } diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 1e3e65761..fa77e95e6 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -2275,7 +2275,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se selFile2 = selFile; } - int aaScale = Configuration.reduceAntialiasConflationByScalingForExport.get() ? Configuration.reduceAntialiasConflationByScalingValueForExport.get() : 1; + int aaScale = Configuration.useMsaaForExport.get() ? Configuration.msaaGridForExport.get() : 1; EventListener evl = swf.getExportEventListener(); @@ -2395,7 +2395,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se return; } - int aaScale = Configuration.reduceAntialiasConflationByScalingForExport.get() ? Configuration.reduceAntialiasConflationByScalingValueForExport.get() : 1; + int aaScale = Configuration.useMsaaForExport.get() ? Configuration.msaaGridForExport.get() : 1; EventListener evl = swf.getExportEventListener(); @@ -2491,7 +2491,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se public void exportAllDebug(SWF swf, AbortRetryIgnoreHandler handler, String selFile, ExportDialog export) throws IOException, InterruptedException { EventListener evl = swf.getExportEventListener(); - int aaScale = Configuration.reduceAntialiasConflationByScalingForExport.get() ? Configuration.reduceAntialiasConflationByScalingValueForExport.get() : 1; + int aaScale = Configuration.useMsaaForExport.get() ? Configuration.msaaGridForExport.get() : 1; if (export.isOptionEnabled(ImageExportMode.class)) { for (ImageExportMode exportMode : ImageExportMode.values()) { @@ -5667,7 +5667,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se try { AbortRetryIgnoreHandler errorHandler = new GuiAbortRetryIgnoreHandler(); - int aaScale = Configuration.reduceAntialiasConflationByScalingForExport.get() ? Configuration.reduceAntialiasConflationByScalingValueForExport.get() : 1; + int aaScale = Configuration.useMsaaForExport.get() ? Configuration.msaaGridForExport.get() : 1; FrameExporter frameExporter = new FrameExporter(); FrameExportSettings fes = new FrameExportSettings(mode, dialog.getZoom(), dialog.isTransparentFrameBackgroundEnabled(), aaScale); diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/antialias16.png b/src/com/jpexs/decompiler/flash/gui/graphics/antialias16.png new file mode 100644 index 000000000..f2b0cece2 Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/antialias16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/antialias32.png b/src/com/jpexs/decompiler/flash/gui/graphics/antialias32.png new file mode 100644 index 000000000..d814a3a9b Binary files /dev/null and b/src/com/jpexs/decompiler/flash/gui/graphics/antialias32.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties index 23f451906..266a55ca0 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties @@ -679,3 +679,15 @@ config.description.svgExportGaussianBlur = Use gaussian blur instead of box blur #after 25.1.3 config.name.exportFlaAs3DisableScriptLayer = FLA export - AS3 - disable script layer config.description.exportFlaAs3DisableScriptLayer = In ActionScript 3 FLA export, the scripts in frames will stay inside class they belong, instead of putting them separately to Script layer. True = in class, False = in Script layer. + +config.name.useMsaaForDisplay = Use Multi sample Anti-aliasing (Display) +config.description.useMsaaForDisplay = Use Multi sample Anti-aliasing 4x4 for display purposes. + +config.name.useMsaaForExport = Use Multi sample Anti-aliasing (Export) +config.description.useMsaaForExport = Use Multi sample Anti-aliasing 4x4 for export purposes. + +config.name.msaaGridForDisplay = Multi sample Anti-aliasing Grid (Display) +config.description.msaaGridForDisplay = Multi sample Anti-aliasing grid size NxN for display purposes. + +config.name.msaaGridForExport = Multi sample Anti-aliasing Grid (Export) +config.description.msaaGridForExport = Multi sample Anti-aliasing grid size NxN for display purposes. diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties index 1737c3b56..cd85aef20 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_cs.properties @@ -679,3 +679,15 @@ config.description.svgExportGaussianBlur = Pou\u017e\u00edt gaussovsk\u00fd blur #after 25.1.3 config.name.exportFlaAs3DisableScriptLayer = Export FLA \u2013 AS3 - zak\u00e1zat vrstvu skript\u016f config.description.exportFlaAs3DisableScriptLayer = P\u0159i exportu ActionScript 3 FLA z\u016fstanou skripty ve sn\u00edmc\u00edch uvnit\u0159 t\u0159\u00eddy, do kter\u00e9 pat\u0159\u00ed, m\u00edsto aby byly odd\u011blen\u011b um\u00edst\u011bny do vrstvy Script. True = ve t\u0159\u00edd\u011b, False = ve vrstv\u011b Script. + +config.name.useMsaaForDisplay = Pou\u017e\u00edt v\u00edcen\u00e1sobn\u00e9 vyhlazov\u00e1n\u00ed (Zobrazen\u00ed) +config.description.useMsaaForDisplay = Pou\u017e\u00edt v\u00edcen\u00e1sobn\u00e9 vyhlazov\u00e1n\u00ed 4x4 pro \u00fa\u010dely zobrazen\u00ed. + +config.name.useMsaaForExport = Pou\u017e\u00edt v\u00edcen\u00e1sobn\u00e9 vyhlazov\u00e1n\u00ed (Export) +config.description.useMsaaForExport = Pou\u017e\u00edt v\u00edcen\u00e1sobn\u00e9 vyhlazov\u00e1n\u00ed 4x4 pro \u00fa\u010dely exportu. + +config.name.msaaGridForDisplay = M\u0159\u00ed\u017eka v\u00edcen\u00e1sobn\u00e9ho vyhlazov\u00e1n\u00ed (Zobrazen\u00ed) +config.description.msaaGridForDisplay = Velikost m\u0159\u00ed\u017eky v\u00edcen\u00e1sobn\u00e9ho vyhlazov\u00e1n\u00ed NxN pro \u00fa\u010dely zobrazen\u00ed. + +config.name.msaaGridForExport = M\u0159\u00ed\u017eka v\u00edcen\u00e1sobn\u00e9ho vyhlazov\u00e1n\u00ed (Export) +config.description.msaaGridForExport = Velikost m\u0159\u00ed\u017eky v\u00edcen\u00e1sobn\u00e9ho vyhlazov\u00e1n\u00ed NxN pro \u00fa\u010dely zobrazen\u00ed. diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_de.properties b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_de.properties index f685431f5..14ecf07f9 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_de.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_de.properties @@ -608,3 +608,15 @@ config.description.svgExportGaussianBlur = Beim SVG-Export den gau\u00dfschen We #after 25.1.3 config.name.exportFlaAs3DisableScriptLayer = FLA-Export \u2013 AS3 - Skriptebene deaktivieren config.description.exportFlaAs3DisableScriptLayer = Beim Export von ActionScript 3 FLA bleiben die Skripte in Frames innerhalb der Klasse, zu der sie geh\u00f6ren, anstatt separat in die Skriptebene verschoben zu werden. True = in der Klasse, False = in der Skriptebene. + +config.name.useMsaaForDisplay = Multisample-Anti-Aliasing verwenden (Anzeige) +config.description.useMsaaForDisplay = Multisample-Anti-Aliasing 4x4 f\u00fcr Anzeigezwecke verwenden. + +config.name.useMsaaForExport = Multisample-Anti-Aliasing verwenden (Export) +config.description.useMsaaForExport = Multisample-Anti-Aliasing 4x4 f\u00fcr Exportzwecke verwenden. + +config.name.msaaGridForDisplay = Multisample-Anti-Aliasing-Raster (Anzeige) +config.description.msaaGridForDisplay = Rastergr\u00f6\u00dfe NxN f\u00fcr Multisample-Anti-Aliasing f\u00fcr Anzeigezwecke. + +config.name.msaaGridForExport = Multisample-Anti-Aliasing-Raster (Export) +config.description.msaaGridForExport = Rastergr\u00f6\u00dfe NxN pro Multisample-Anti-Aliasing pro Anzeigezwecke. diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_sk.properties b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_sk.properties index 57cabee11..bd6202ef8 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_sk.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog_sk.properties @@ -679,3 +679,15 @@ config.description.svgExportGaussianBlur = Pou\u017ei\u0165 gaussovsk\u00fd blur #after 25.1.3 config.name.exportFlaAs3DisableScriptLayer = Export FLA \u2013 AS3 - zak\u00e1za\u0165 vrstvu skriptov config.description.exportFlaAs3DisableScriptLayer = Pri exporte ActionScript 3 FLA zostan\u00fa skripty v sn\u00edmkach vn\u00fatri triedy, do ktorej patria, namiesto toho, aby boli oddelene umiestnen\u00e9 do vrstvy Script. True = v triede, False = vo vrstve Script. + +config.name.useMsaaForDisplay = Pou\u017ei\u0165 viacn\u00e1sobn\u00e9 vyhladzovanie (Zobrazenie) +config.description.useMsaaForDisplay = Pou\u017ei\u0165 viacn\u00e1sobn\u00e9 vyhladzovanie 4x4 na \u00fa\u010dely zobrazenia. + +config.name.useMsaaForExport = Pou\u017ei\u0165 viacn\u00e1sobn\u00e9 vyhladzovanie (Export) +config.description.useMsaaForExport = Pou\u017ei\u0165 viacn\u00e1sobn\u00e9 vyhladzovanie 4x4 na \u00fa\u010dely exportu. + +config.name.msaaGridForDisplay = Mrie\u017eka viacn\u00e1sobn\u00e9ho vyhladzovania (Zobrazenie) +config.description.msaaGridForDisplay = Ve\u013ekos\u0165 mrie\u017eky viacn\u00e1sobn\u00e9ho vyhladzovania NxN na \u00fa\u010dely zobrazenia. + +config.name.msaaGridForExport = Mrie\u017eka viacn\u00e1sobn\u00e9ho vyhladzovania (Export) +config.description.msaaGridForExport = Ve\u013ekos\u0165 mrie\u017eky viacn\u00e1sobn\u00e9ho vyhladzovania NxN na \u00fa\u010dely zobrazenia. diff --git a/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog.properties index f2fd7149c..669750597 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog.properties @@ -124,3 +124,6 @@ morphshapes.apng = APNG #after 25.1.3 buttons.svg_combined = SVG combined + +antialias = Use advanced MSAA renderer (slow) +antialias.hint = Use advanced renderer with multi sample anti-aliasing. More precise, but slower than standard renderer. Use for seamless shape rendering. diff --git a/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog_cs.properties index 84283332d..a2419179e 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog_cs.properties @@ -122,3 +122,7 @@ arrow = \ud83e\udc06 #after 25.1.3 buttons.svg_combined = SVG kombinovan\u00e9 + +antialias = Pou\u017e\u00edt pokro\u010dil\u00fd MSAA renderer (pomal\u00e9) +antialias.hint = Pou\u017e\u00edt pokro\u010dil\u00fd renderer s v\u00edcen\u00e1sobn\u00fdm vyhlazov\u00e1n\u00edm. P\u0159esn\u011bj\u0161\u00ed, ale pomalej\u0161\u00ed ne\u017e standardn\u00ed renderer. Vhodn\u00e9 pro vykreslov\u00e1n\u00ed tvar\u016f bez \u0161v\u016f. + diff --git a/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog_de.properties b/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog_de.properties index 2a2af8eda..a0c56ac00 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog_de.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog_de.properties @@ -119,3 +119,6 @@ morphshapes.apng = APNG #after 25.1.3 buttons.svg_combined = SVG kombiniertes + +antialias = Erweiterter MSAA-Renderer verwenden (langsam) +antialias.hint = Erweiterter Renderer mit Multisample-Anti-Aliasing verwenden. Pr\u00e4ziser, aber langsamer als der Standard-Renderer. Geeignet f\u00fcr nahtloses Rendern ohne sichtbare \u00dcberg\u00e4nge zwischen Formen. diff --git a/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog_sk.properties b/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog_sk.properties index 7c6f82988..573ea87d0 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog_sk.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/ExportDialog_sk.properties @@ -124,3 +124,6 @@ morphshapes.apng = APNG #after 25.1.3 buttons.svg_combined = SVG kombinovan\u00e9 + +antialias = Pou\u017ei\u0165 pokro\u010dil\u00fd MSAA renderer (pomal\u00e9) +antialias.hint = Pou\u017ei\u0165 pokro\u010dil\u00fd renderer s viacn\u00e1sobn\u00fdm vyhladzovan\u00edm. Presnej\u0161\u00ed, ale pomal\u0161\u00ed ne\u017e \u0161tandardn\u00fd renderer. Vhodn\u00e9 pre vykres\u013eovanie tvarov bez \u0161vov. diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 13104dcb9..a3a38c051 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1149,4 +1149,6 @@ contextmenu.exportXaml = Export XAML menu.file.import.bulkImport = Bulk import... menu.file.import.createTagFromFile = Create tag from file... -contextmenu.convertTextType = Convert text type \ No newline at end of file +contextmenu.convertTextType = Convert text type + +button.antialias.hint = Use advanced renderer with multi sample anti-aliasing (slow) 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 e73f3025a..05d2bef07 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties @@ -1149,4 +1149,6 @@ contextmenu.exportXaml = Exportovat XAML menu.file.import.bulkImport = Hromadn\u00fd import... menu.file.import.createTagFromFile = Vytvo\u0159it tag ze souboru... -contextmenu.convertTextType = Konvertovat typ textu \ No newline at end of file +contextmenu.convertTextType = Konvertovat typ textu + +button.antialias.hint = Pou\u017e\u00edt pokro\u010dil\u00fd renderer s v\u00edcen\u00e1sobn\u00fdm vyhlazov\u00e1n\u00edm (pomal\u00e9) diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_de.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_de.properties index 6006bae78..bcc565dac 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_de.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_de.properties @@ -1010,4 +1010,6 @@ contextmenu.exportXaml = Exportiere als XAML menu.file.import.bulkImport = Massenimport... menu.file.import.createTagFromFile = Tag aus Datei erstellen... -contextmenu.convertTextType = Texttyp konvertieren \ No newline at end of file +contextmenu.convertTextType = Texttyp konvertieren + +button.antialias.hint = Erweiterter Renderer mit Multisample-Anti-Aliasing verwenden (langsam) diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_sk.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_sk.properties index fd3269661..5d54f01f9 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_sk.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_sk.properties @@ -1149,4 +1149,6 @@ contextmenu.exportXaml = Exportova\u0165 XAML menu.file.import.bulkImport = Hromadn\u00fd import... menu.file.import.createTagFromFile = Vytvori\u0165 tag zo s\u00faboru... -contextmenu.convertTextType = Konvertova\u0165 typ textu \ No newline at end of file +contextmenu.convertTextType = Konvertova\u0165 typ textu + +button.antialias.hint = Pou\u017ei\u0165 pokro\u010dil\u00fd renderer s viacn\u00e1sobn\u00fdm vyhladzovan\u00edm (pomal\u00e9) diff --git a/src/com/jpexs/decompiler/flash/gui/player/PlayerControls.java b/src/com/jpexs/decompiler/flash/gui/player/PlayerControls.java index 228da8e29..459ca998a 100644 --- a/src/com/jpexs/decompiler/flash/gui/player/PlayerControls.java +++ b/src/com/jpexs/decompiler/flash/gui/player/PlayerControls.java @@ -17,6 +17,7 @@ package com.jpexs.decompiler.flash.gui.player; import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.configuration.ConfigurationItemChangeListener; import com.jpexs.decompiler.flash.gui.AppStrings; import com.jpexs.decompiler.flash.gui.MainPanel; import com.jpexs.decompiler.flash.gui.View; @@ -125,7 +126,9 @@ public class PlayerControls extends JPanel implements MediaDisplayListener { private final int zeroCharacterWidth; - private JButton selectColorButton; + private final JButton selectColorButton; + + private JToggleButton msaaButton; static { Font font = new JLabel().getFont(); @@ -152,6 +155,19 @@ public class PlayerControls extends JPanel implements MediaDisplayListener { selectColorButton = new JButton(View.getIcon("color16")); selectColorButton.addActionListener(this::selectBkColorButtonActionPerformed); selectColorButton.setToolTipText(AppStrings.translate("button.selectbkcolor.hint")); + + msaaButton = new JToggleButton(View.getIcon("antialias16")); + msaaButton.addActionListener(this::msaaButtonActionPerformed); + msaaButton.setToolTipText(AppStrings.translate("button.antialias.hint")); + msaaButton.setSelected(Configuration.useMsaaForDisplay.get()); + + Configuration.useMsaaForDisplay.addListener(new ConfigurationItemChangeListener() { + @Override + public void configurationItemChanged(Boolean newValue) { + msaaButton.setSelected(newValue); + } + }); + zoomPanel = new ZoomPanel(display); zoomPanel.setVisible(false); @@ -162,6 +178,7 @@ public class PlayerControls extends JPanel implements MediaDisplayListener { snapshotButton.setVisible(false); graphicButtonsPanel.add(zoomPanel); + graphicButtonsPanel.add(msaaButton); graphicButtonsPanel.add(selectColorButton); graphicButtonsPanel.add(snapshotButton); @@ -499,6 +516,10 @@ public class PlayerControls extends JPanel implements MediaDisplayListener { putImageToClipBoard(display.printScreen()); } + private void msaaButtonActionPerformed(ActionEvent evt) { + Configuration.useMsaaForDisplay.set(msaaButton.isSelected()); + } + private void showButtonActionPerformed(ActionEvent evt) { display.setDisplayed(showButton.isSelected()); if (!showButton.isSelected()) {