Fixed: #2596 Scaling images in relation to antialias conflation

This commit is contained in:
Jindra Petřík
2026-01-03 14:09:18 +01:00
parent ebf31a40c8
commit 41b9d1874b
5 changed files with 107 additions and 27 deletions
@@ -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;
}
@@ -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) {
@@ -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<File> exportShapes(AbortRetryIgnoreHandler handler, final String outdir, final SWF swf, ReadOnlyTagList tags, final ShapeExportSettings settings, EventListener evl, double unzoom, int aaScale) throws IOException, InterruptedException {
List<File> 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) {
@@ -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;
}
}