diff --git a/CHANGELOG.md b/CHANGELOG.md index dc41276e0..4e4646889 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ All notable changes to this project will be documented in this file. - Save as EXE through context menu of SWF files (previously only in toolbar) - Slovenian translation - SVG export - video frames -- [#2592], [#2154], [#2591] - Better handling of antialias conflation artifacts (by scaling), +- [#2592], [#2154], [#2591], [#2596] - Better handling of antialias conflation artifacts (by scaling), can be enabled in Advanced Settings / Display and Export. ### Fixed @@ -4075,6 +4075,7 @@ Major version of SWF to XML export changed to 2. [#2592]: https://www.free-decompiler.com/flash/issues/2592 [#2154]: https://www.free-decompiler.com/flash/issues/2154 [#2591]: https://www.free-decompiler.com/flash/issues/2591 +[#2596]: https://www.free-decompiler.com/flash/issues/2596 [#2570]: https://www.free-decompiler.com/flash/issues/2570 [#2571]: https://www.free-decompiler.com/flash/issues/2571 [#2575]: https://www.free-decompiler.com/flash/issues/2575 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 1d521fc0c..32dd06e8b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWF.java @@ -177,6 +177,7 @@ import com.jpexs.helpers.ByteArrayRange; import com.jpexs.helpers.Cache; import com.jpexs.helpers.CancellableWorker; import com.jpexs.helpers.Helper; +import com.jpexs.helpers.ImageResizer; import com.jpexs.helpers.ImmediateFuture; import com.jpexs.helpers.NulStream; import com.jpexs.helpers.ProgressListener; @@ -189,6 +190,7 @@ import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; +import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; @@ -5025,12 +5027,10 @@ public final class SWF implements SWFContainerItem, Timelined, Openable { 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); - - SerializableImage img2 = new SerializableImage(image.getWidth() / aaScale, image.getHeight() / aaScale, BufferedImage.TYPE_INT_ARGB_PRE); - img2.fillTransparent(); - Graphics2D g2 = (Graphics2D) img2.getGraphics(); - g2.drawImage(image.getBufferedImage().getScaledInstance(image.getWidth() / aaScale, image.getHeight() / aaScale, Image.SCALE_SMOOTH), 0, 0, null); - image = img2; + + if (aaScale > 1) { + image = new SerializableImage(ImageResizer.resizeImage(image.getBufferedImage(), image.getWidth() / aaScale, image.getHeight() / aaScale, RenderingHints.VALUE_INTERPOLATION_BICUBIC, true)); + } return image; } 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 944fcf4cd..d8f116297 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 @@ -45,6 +45,7 @@ import com.jpexs.decompiler.flash.types.RGB; import com.jpexs.decompiler.graph.DottedChain; import com.jpexs.helpers.CancellableWorker; import com.jpexs.helpers.Helper; +import com.jpexs.helpers.ImageResizer; import com.jpexs.helpers.Path; import com.jpexs.helpers.SerializableImage; import com.jpexs.helpers.utf8.Utf8Helper; @@ -52,6 +53,7 @@ import dev.matrixlab.webp4j.WebPCodec; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; +import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; @@ -188,12 +190,8 @@ public class MorphShapeExporter { BufferedImage bim = img.getBufferedImage(); if (realAaScale > 1) { - SerializableImage img2 = new SerializableImage(((newWidth - 1) / realAaScale) + 1, - ((newHeight - 1) / realAaScale) + 1, SerializableImage.TYPE_INT_ARGB_PRE); - img2.fillTransparent(); - Graphics g2 = img2.getGraphics(); - g2.drawImage(img.getBufferedImage().getScaledInstance(img2.getWidth(), img2.getHeight(), Image.SCALE_SMOOTH), 0, 0, null); - bim = img2.getBufferedImage(); + bim = ImageResizer.resizeImage(bim, ((newWidth - 1) / realAaScale) + 1, + ((newHeight - 1) / realAaScale) + 1, RenderingHints.VALUE_INTERPOLATION_BICUBIC, true); } if (settings.mode == MorphShapeExportMode.PNG_START_END) { @@ -229,12 +227,8 @@ public class MorphShapeExporter { bim = img.getBufferedImage(); if (realAaScale > 1) { - SerializableImage img2 = new SerializableImage(((newWidth - 1) / realAaScale) + 1, - ((newHeight - 1) / realAaScale) + 1, SerializableImage.TYPE_INT_ARGB_PRE); - img2.fillTransparent(); - Graphics g2 = img2.getGraphics(); - g2.drawImage(img.getBufferedImage().getScaledInstance(img2.getWidth(), img2.getHeight(), Image.SCALE_SMOOTH), 0, 0, null); - bim = img2.getBufferedImage(); + bim = ImageResizer.resizeImage(bim, ((newWidth - 1) / realAaScale) + 1, + ((newHeight - 1) / realAaScale) + 1, RenderingHints.VALUE_INTERPOLATION_BICUBIC, true); } if (settings.mode == MorphShapeExportMode.PNG_START_END) { 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 160b82275..e20a0729b 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 @@ -43,6 +43,7 @@ import com.jpexs.decompiler.flash.types.SHAPE; import com.jpexs.decompiler.graph.DottedChain; import com.jpexs.helpers.CancellableWorker; import com.jpexs.helpers.Helper; +import com.jpexs.helpers.ImageResizer; import com.jpexs.helpers.Path; import com.jpexs.helpers.SerializableImage; import com.jpexs.helpers.utf8.Utf8Helper; @@ -50,6 +51,10 @@ import dev.matrixlab.webp4j.WebPCodec; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; +import java.awt.RenderingHints; +import java.awt.Transparency; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; @@ -72,8 +77,8 @@ import java.util.logging.Logger; * * @author JPEXS */ -public class ShapeExporter { - +public class ShapeExporter { + public List exportShapes(AbortRetryIgnoreHandler handler, final String outdir, final SWF swf, ReadOnlyTagList tags, final ShapeExportSettings settings, EventListener evl, double unzoom, int aaScale) throws IOException, InterruptedException { List ret = new ArrayList<>(); if (CancellableWorker.isInterrupted()) { @@ -147,12 +152,8 @@ public class ShapeExporter { BufferedImage bim = img.getBufferedImage(); if (realAaScale > 1) { - SerializableImage img2 = new SerializableImage(((newWidth - 1) / realAaScale) + 1, - ((newHeight - 1) / realAaScale) + 1, SerializableImage.TYPE_INT_ARGB_PRE); - img2.fillTransparent(); - Graphics g2 = img2.getGraphics(); - g2.drawImage(img.getBufferedImage().getScaledInstance(img2.getWidth(), img2.getHeight(), Image.SCALE_SMOOTH), 0, 0, null); - bim = img2.getBufferedImage(); + bim = ImageResizer.resizeImage(bim, ((newWidth - 1) / realAaScale) + 1, + ((newHeight - 1) / realAaScale) + 1, RenderingHints.VALUE_INTERPOLATION_BICUBIC, true); } if (settings.mode == ShapeExportMode.PNG) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/ImageResizer.java b/libsrc/ffdec_lib/src/com/jpexs/helpers/ImageResizer.java new file mode 100644 index 000000000..48dc05a17 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/ImageResizer.java @@ -0,0 +1,84 @@ +package com.jpexs.helpers; + +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; + +/** + * Image resizer. Correct downscaling of images. + * + * @author JPEXS + * + * Based on + * https://web.archive.org/web/20150504200847/https://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html + */ +public class ImageResizer { + + /** + * Returns a scaled instance of the provided {@code BufferedImage}. + * + * + * @param img the original image to be scaled + * @param targetWidth the desired width of the scaled instance, in pixels + * @param targetHeight the desired height of the scaled instance, in pixels + * @param hint one of the rendering hints that corresponds to + * {@code RenderingHints.KEY_INTERPOLATION} (e.g. + * {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR}, + * {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR}, + * {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC}) + * @param higherQuality if true, this method will use a multi-step scaling + * technique that provides higher quality than the usual one-step technique + * (only useful in downscaling cases, where {@code targetWidth} or + * {@code targetHeight} is smaller than the original dimensions, and + * generally only when the {@code BILINEAR} hint is specified) + * @return a scaled version of the original {@code BufferedImage} + */ + public static BufferedImage resizeImage(BufferedImage img, + int targetWidth, + int targetHeight, + Object hint, + boolean higherQuality) { + int type = img.getType(); + BufferedImage ret = (BufferedImage) img; + int w; + int h; + if (higherQuality) { + // Use multi-step technique: start with original size, then + // scale down in multiple passes with drawImage() + // until the target size is reached + w = img.getWidth(); + h = img.getHeight(); + } else { + // Use one-step technique: scale directly from original + // size to target size with a single drawImage() call + w = targetWidth; + h = targetHeight; + } + + do { + if (higherQuality && w > targetWidth) { + w /= 2; + if (w < targetWidth) { + w = targetWidth; + } + } + + if (higherQuality && h > targetHeight) { + h /= 2; + if (h < targetHeight) { + h = targetHeight; + } + } + + BufferedImage tmp = new BufferedImage(w, h, type); + Graphics2D g2 = tmp.createGraphics(); + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint); + g2.drawImage(ret, 0, 0, w, h, null); + g2.dispose(); + + ret = tmp; + } while (w != targetWidth || h != targetHeight); + + return ret; + } +}