Merge pull request #737 from tc-wleite/WebP_FixAlphaSubsampling

WebP: Fix alpha decoding when source subsampling is used
This commit is contained in:
Harald Kuhr 2023-03-16 11:59:27 +01:00 committed by GitHub
commit 614a07e040
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 66 additions and 31 deletions

View File

@ -61,6 +61,8 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import static com.twelvemonkeys.imageio.plugins.webp.lossless.VP8LDecoder.copyIntoRasterWithParams;
import static java.lang.Math.max;
import static java.lang.Math.min;
@ -536,21 +538,26 @@ final class WebPImageReader extends ImageReaderBase {
// Simulate header
imageInput.seek(imageInput.getStreamPosition() - 5);
WritableRaster tempRaster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, destination.getWidth(), destination.getHeight(), 4, null);
readVP8Lossless(tempRaster, param, width, height);
// Temp alpha raster must have same dimensions as the source, because of filtering.
WritableRaster tempRaster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height, 4, null);
readVP8Lossless(tempRaster, null, width, height);
// Copy from green (band 1) in temp to alpha in destination
alphaRaster.setRect(tempRaster.createChild(0, 0, tempRaster.getWidth(), tempRaster.getHeight(), 0, 0, new int[] {1}));
WritableRaster alphaChannel = tempRaster.createWritableChild(0, 0, tempRaster.getWidth(), tempRaster.getHeight(), 0, 0, new int[]{1});
alphaFilter(alphaChannel, filtering);
copyIntoRasterWithParams(alphaChannel, alphaRaster, param);
break;
default:
processWarningOccurred("Unknown WebP alpha compression: " + compression);
opaqueAlpha(alphaRaster);
break;
}
}
private void alphaFilter(WritableRaster alphaRaster, int filtering) {
if (filtering != AlphaFiltering.NONE) {
for (int y = 0; y < destination.getHeight(); y++) {
for (int x = 0; x < destination.getWidth(); x++) {
for (int y = 0; y < alphaRaster.getHeight(); y++) {
for (int x = 0; x < alphaRaster.getWidth(); x++) {
int predictorAlpha = getPredictorAlpha(alphaRaster, filtering, y, x);
alphaRaster.setSample(x, y, 0, alphaRaster.getSample(x, y, 0) + predictorAlpha % 256);
}

View File

@ -135,33 +135,36 @@ public final class VP8LDecoder {
}
if (fullSizeRaster != raster && param != null) {
// Copy into destination raster with settings applied
Rectangle sourceRegion = param.getSourceRegion();
int sourceXSubsampling = param.getSourceXSubsampling();
int sourceYSubsampling = param.getSourceYSubsampling();
int subsamplingXOffset = param.getSubsamplingXOffset();
int subsamplingYOffset = param.getSubsamplingYOffset();
Point destinationOffset = param.getDestinationOffset();
copyIntoRasterWithParams(fullSizeRaster, raster, param);
}
}
/**
* Copy a source raster into a destination raster with settings applied.
*/
public static void copyIntoRasterWithParams(final Raster srcRaster, final WritableRaster dstRaster,
final ImageReadParam param) {
Rectangle sourceRegion = param != null && param.getSourceRegion() != null ? param.getSourceRegion() : dstRaster.getBounds();
int sourceXSubsampling = param != null ? param.getSourceXSubsampling() : 1;
int sourceYSubsampling = param != null ? param.getSourceYSubsampling() : 1;
int subsamplingXOffset = param != null ? param.getSubsamplingXOffset() : 0;
int subsamplingYOffset = param != null ? param.getSubsamplingYOffset() : 0;
Point destinationOffset = param != null ? param.getDestinationOffset() : new Point(0, 0) ;
if (sourceRegion == null) {
sourceRegion = raster.getBounds();
}
if (sourceXSubsampling == 1 && sourceYSubsampling == 1) {
// Only apply offset (and limit to requested region)
dstRaster.setRect(destinationOffset.x, destinationOffset.y, srcRaster);
}
else {
// Manual copy, more efficient way might exist
byte[] rgba = new byte[4];
int xEnd = dstRaster.getWidth() + dstRaster.getMinX();
int yEnd = dstRaster.getHeight() + dstRaster.getMinY();
if (sourceXSubsampling == 1 && sourceYSubsampling == 1) {
// Only apply offset (and limit to requested region)
raster.setRect(destinationOffset.x, destinationOffset.y, fullSizeRaster);
}
else {
// Manual copy, more efficient way might exist
byte[] rgba = new byte[4];
int xEnd = raster.getWidth() + raster.getMinX();
int yEnd = raster.getHeight() + raster.getMinY();
for (int xDst = destinationOffset.x, xSrc = sourceRegion.x + subsamplingXOffset; xDst < xEnd; xDst++, xSrc += sourceXSubsampling) {
for (int yDst = destinationOffset.y, ySrc = sourceRegion.y + subsamplingYOffset; yDst < yEnd; yDst++, ySrc += sourceYSubsampling) {
fullSizeRaster.getDataElements(xSrc, ySrc, rgba);
raster.setDataElements(xDst, yDst, rgba);
}
for (int xDst = destinationOffset.x, xSrc = sourceRegion.x + subsamplingXOffset; xDst < xEnd; xDst++, xSrc += sourceXSubsampling) {
for (int yDst = destinationOffset.y, ySrc = sourceRegion.y + subsamplingYOffset; yDst < yEnd; yDst++, ySrc += sourceYSubsampling) {
srcRaster.getDataElements(xSrc, ySrc, rgba);
dstRaster.setDataElements(xDst, yDst, rgba);
}
}
}

View File

@ -58,7 +58,9 @@ public class WebPImageReaderTest extends ImageReaderAbstractTest<WebPImageReader
new Dimension(400, 400), new Dimension(400, 400), new Dimension(400, 394),
new Dimension(371, 394), new Dimension(394, 382), new Dimension(400, 388),
new Dimension(394, 383), new Dimension(394, 394), new Dimension(372, 394),
new Dimension(400, 400), new Dimension(320, 382))
new Dimension(400, 400), new Dimension(320, 382)),
// Alpha transparency and Alpha filtering
new TestData(getClassLoaderResource("/webp/alpha_filter.webp"), new Dimension(1600, 1600))
);
}
@ -162,4 +164,27 @@ public class WebPImageReaderTest extends ImageReaderAbstractTest<WebPImageReader
reader.dispose();
}
}
@Test
public void testAlphaSubsampling() throws IOException {
WebPImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/webp/alpha_filter.webp"))) {
reader.setInput(stream);
// Read the image using a subsampling factor of 2
ImageReadParam param = new ImageReadParam();
param.setSourceSubsampling(2, 2, 0, 0);
BufferedImage image = reader.read(0, param);
assertRGBEquals("Expected transparent at (100, 265)", 0x00000000, image.getRGB(100, 265) & 0xFF000000, 8);
assertRGBEquals("Expected transparent at (512, 320)", 0x00000000, image.getRGB(512, 320) & 0xFF000000, 8);
assertRGBEquals("Expected opaque at (666, 444)", 0xFF000000, image.getRGB(666, 444) & 0xFF000000, 8);
assertRGBEquals("Expected opaque corner (799, 799)", 0xFF000000, image.getRGB(699, 699) & 0xFF000000, 8);
}
finally {
reader.dispose();
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB