Add aspect-preserving opaque image fitting
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
# Opaque image fitting
|
||||
|
||||
`ImageScaler.fitOpaque(source, maxWidth, maxHeight, background)` returns an
|
||||
independent RGB BufferedImage. It fits within positive bounds without enlargement,
|
||||
keeps aspect ratio to the nearest pixel (minimum one pixel per dimension), and
|
||||
composites transparent pixels onto the supplied opaque AWT Color. Bilinear
|
||||
interpolation is used. The source is unchanged and graphics resources are released.
|
||||
|
||||
This operation is independent of file format and wire encoding. Callers own image
|
||||
decoding, any source subsampling, and target quantization. Existing PNG scaling
|
||||
methods retain their original behavior.
|
||||
|
||||
`ImageScalerTest` verifies fitting, no enlargement, transparent/partial-alpha
|
||||
composition, narrow images, source preservation, and invalid bounds.
|
||||
@@ -17,6 +17,41 @@ import java.io.InputStream;
|
||||
@Slf4j
|
||||
public class ImageScaler {
|
||||
|
||||
/**
|
||||
* Fits an image within positive bounds without enlargement, retaining aspect ratio
|
||||
* to the nearest pixel (at least one pixel per dimension). Composites alpha onto
|
||||
* the supplied opaque background and returns an independent RGB image.
|
||||
*
|
||||
* @param source source pixels, never modified
|
||||
* @param maxWidth maximum output width
|
||||
* @param maxHeight maximum output height
|
||||
* @param background opaque background colour
|
||||
* @return fitted image with no alpha channel
|
||||
* @throws IllegalArgumentException for non-positive bounds or translucent background
|
||||
*/
|
||||
public static BufferedImage fitOpaque(final BufferedImage source, final int maxWidth,
|
||||
final int maxHeight, final Color background) {
|
||||
java.util.Objects.requireNonNull(source, "source");
|
||||
java.util.Objects.requireNonNull(background, "background");
|
||||
if (maxWidth < 1 || maxHeight < 1 || background.getAlpha() != 255)
|
||||
throw new IllegalArgumentException("Positive bounds and an opaque background are required");
|
||||
double scale = Math.min(1.0, Math.min((double) maxWidth / source.getWidth(),
|
||||
(double) maxHeight / source.getHeight()));
|
||||
int width = Math.max(1, (int) Math.round(source.getWidth() * scale));
|
||||
int height = Math.max(1, (int) Math.round(source.getHeight() * scale));
|
||||
BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D graphics = result.createGraphics();
|
||||
try {
|
||||
graphics.setColor(background);
|
||||
graphics.fillRect(0, 0, width, height);
|
||||
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
graphics.drawImage(source, 0, 0, width, height, null);
|
||||
} finally {
|
||||
graphics.dispose();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the target image format for scaling operations.
|
||||
* <p>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package de.neitzel.core.image;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.image.BufferedImage;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ImageScalerTest {
|
||||
@Test void fitsWithoutUpscalingAndCompositesTransparency() {
|
||||
BufferedImage source = new BufferedImage(128,64,BufferedImage.TYPE_INT_ARGB);
|
||||
BufferedImage result = ImageScaler.fitOpaque(source,64,64,Color.BLUE);
|
||||
assertEquals(64,result.getWidth());
|
||||
assertEquals(32,result.getHeight());
|
||||
assertEquals(Color.BLUE.getRGB(),result.getRGB(0,0));
|
||||
assertEquals(0,source.getRGB(0,0));
|
||||
BufferedImage tiny = new BufferedImage(1,1,BufferedImage.TYPE_INT_ARGB);
|
||||
tiny.setRGB(0,0,0x80ff0000);
|
||||
result = ImageScaler.fitOpaque(tiny,64,64,Color.BLUE);
|
||||
assertEquals(1,result.getWidth());
|
||||
Color mixed = new Color(result.getRGB(0,0));
|
||||
assertEquals(128,mixed.getRed(),1);
|
||||
assertEquals(127,mixed.getBlue(),1);
|
||||
BufferedImage narrow = ImageScaler.fitOpaque(new BufferedImage(1,1000,BufferedImage.TYPE_INT_ARGB),64,64,Color.BLACK);
|
||||
assertEquals(1,narrow.getWidth()); assertEquals(64,narrow.getHeight());
|
||||
assertThrows(IllegalArgumentException.class, () -> ImageScaler.fitOpaque(tiny,0,64,Color.BLACK));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user