Skip to content

Commit

Permalink
Fix PNG image compression PNG image compression (#268)
Browse files Browse the repository at this point in the history
  • Loading branch information
sksamuel committed Jun 11, 2023
1 parent a8378de commit 5f97f1b
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 77 deletions.
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
138 changes: 63 additions & 75 deletions scrimage-core/src/main/java/com/sksamuel/scrimage/nio/PngWriter.java
Expand Up @@ -16,88 +16,76 @@

package com.sksamuel.scrimage.nio;

import ar.com.hjg.pngj.FilterType;
import ar.com.hjg.pngj.ImageInfo;
import ar.com.hjg.pngj.ImageLineInt;
import com.sksamuel.scrimage.AwtImage;
import com.sksamuel.scrimage.metadata.ImageMetadata;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.SinglePixelPackedSampleModel;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.stream.ImageOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class PngWriter implements ImageWriter {

public static final PngWriter MaxCompression = new PngWriter(9);
public static final PngWriter MinCompression = new PngWriter(1);
public static final PngWriter NoCompression = new PngWriter(0);

private final int compressionLevel;

public PngWriter() {
this.compressionLevel = 9;
}

public PngWriter(int compressionLevel) {
this.compressionLevel = compressionLevel;
}

// require(compressionLevel >= 0 && compressionLevel < 10, "Compression level must be between 0 (none) and 9 (max)")

public PngWriter withMaxCompression() {
return MaxCompression;
}

public PngWriter withMinCompression() {
return MinCompression;
}

public PngWriter withCompression(int compression) {
return new PngWriter(compression);
}

@Override
public void write(AwtImage image, ImageMetadata metadata, OutputStream out) throws IOException {

if (image.awt().getType() == BufferedImage.TYPE_INT_ARGB) {

ImageInfo imi = new ImageInfo(image.width, image.height, 8, true);

ar.com.hjg.pngj.PngWriter writer = new ar.com.hjg.pngj.PngWriter(out, imi);
writer.setCompLevel(compressionLevel);
writer.setFilterType(FilterType.FILTER_DEFAULT);

DataBufferInt db = (DataBufferInt) image.awt().getRaster().getDataBuffer();
if (db.getNumBanks() != 1) throw new RuntimeException("This method expects one bank");

SinglePixelPackedSampleModel samplemodel = (SinglePixelPackedSampleModel) image.awt().getSampleModel();
ImageLineInt line = new ImageLineInt(imi);
int[] dbbuf = db.getData();

for (int row = 0; row < imi.rows; row++) {
int elem = samplemodel.getOffset(0, row);
int j = 0;
for (int col = 0; col < imi.cols; col++) {
int sample = dbbuf[elem];
elem = elem + 1;
line.getScanline()[j] = (sample & 0xFF0000) >> 16; // R
j = j + 1;
line.getScanline()[j] = (sample & 0xFF00) >> 8; // G
j = j + 1;
line.getScanline()[j] = sample & 0xFF; // B
j = j + 1;
line.getScanline()[j] = ((sample & 0xFF000000) >> 24) & 0xFF; // A
j = j + 1;
}
writer.writeRow(line, row);
}
writer.end();// end calls close

} else {
ImageIO.write(image.awt(), "png", out);
}
}
public static final PngWriter MaxCompression = new PngWriter(9);
public static final PngWriter MinCompression = new PngWriter(1);
public static final PngWriter NoCompression = new PngWriter(0);

private final int compressionLevel;

public PngWriter() {
this.compressionLevel = 9;
}

public PngWriter(int compressionLevel) {
this.compressionLevel = compressionLevel;
}

// require(compressionLevel >= 0 && compressionLevel < 10, "Compression level must be between 0 (none) and 9 (max)")

public PngWriter withMaxCompression() {
return MaxCompression;
}

public PngWriter withMinCompression() {
return MinCompression;
}

public PngWriter withCompression(int compression) {
return new PngWriter(compression);
}

@Override
public void write(AwtImage image, ImageMetadata metadata, OutputStream out) throws IOException {

ImageTypeSpecifier type = ImageTypeSpecifier.createFromBufferedImageType(image.getType());
javax.imageio.ImageWriter writer = ImageIO.getImageWriters(type, "png").next();
ImageWriteParam param = writer.getDefaultWriteParam();

if (param.canWriteCompressed()) {
switch (compressionLevel) {
case 9: // max
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(0.0f);
break;
case 1: // min
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(1.0f);
break;
case 0: // none
param.setCompressionMode(ImageWriteParam.MODE_DISABLED);
default:
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(compressionLevel / 10f);
}
}

ImageOutputStream ios = ImageIO.createImageOutputStream(out);
writer.setOutput(ios);
writer.write(null, new IIOImage(image.awt(), null, null), param);
writer.dispose();
ios.close();
}
}
1 change: 0 additions & 1 deletion scrimage-tests/build.gradle.kts
Expand Up @@ -9,7 +9,6 @@ dependencies {
implementation("com.drewnoakes:metadata-extractor:2.18.0")
implementation("com.github.zh79325:open-gif:1.0.4")
implementation("commons-io:commons-io:2.11.0")
implementation("ar.com.hjg:pngj:2.1.0")
implementation(project(":scrimage-core"))
testImplementation(kotlin("stdlib"))
testImplementation(kotlin("stdlib-jdk8"))
Expand Down
@@ -0,0 +1,20 @@
package com.sksamuel.scrimage.core

import com.sksamuel.scrimage.ImmutableImage
import com.sksamuel.scrimage.nio.PngWriter
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.ints.shouldBeLessThan
import java.awt.image.BufferedImage

class Issue267Test : FunSpec() {
init {
test("png compression for all types") {
val originalImage = ImmutableImage.loader().fromResource("/issue267.png")
originalImage.copy(BufferedImage.TYPE_4BYTE_ABGR).bytes(PngWriter.MaxCompression).size shouldBeLessThan 15000
originalImage.copy(BufferedImage.TYPE_3BYTE_BGR).bytes(PngWriter.MaxCompression).size shouldBeLessThan 15000
originalImage.copy(BufferedImage.TYPE_INT_ARGB).bytes(PngWriter.MaxCompression).size shouldBeLessThan 15000
originalImage.copy(BufferedImage.TYPE_INT_BGR).bytes(PngWriter.MaxCompression).size shouldBeLessThan 15000
originalImage.copy(BufferedImage.TYPE_INT_RGB).bytes(PngWriter.MaxCompression).size shouldBeLessThan 15000
}
}
}
Binary file added scrimage-tests/src/test/resources/issue267.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 5f97f1b

Please sign in to comment.