From 38cb1e09ed9e64c2d94f93acbb5e0f30a2bc35bd Mon Sep 17 00:00:00 2001 From: Edouard DUPIN Date: Sat, 24 May 2025 00:24:25 +0200 Subject: [PATCH] [FIX] arbo --- pom.xml | 47 +--- src/module-info.java | 6 - src/org/atriasoft/pngencoder/PngEncoder.java | 173 --------------- .../PngEncoderCountingOutputStream.java | 30 --- .../pngencoder/PngEncoderDeflaterBuffer.java | 32 --- .../PngEncoderDeflaterBufferPool.java | 35 --- .../PngEncoderDeflaterExecutorService.java | 19 -- ...rDeflaterExecutorServiceThreadFactory.java | 31 --- .../PngEncoderDeflaterOutputStream.java | 206 ------------------ .../PngEncoderDeflaterSegmentResult.java | 59 ----- .../PngEncoderDeflaterSegmentTask.java | 41 ---- ...PngEncoderDeflaterThreadLocalDeflater.java | 33 --- .../PngEncoderIdatChunksOutputStream.java | 87 -------- .../atriasoft/pngencoder/PngEncoderLogic.java | 140 ------------ .../PngEncoderPhysicalPixelDimensions.java | 123 ----------- .../PngEncoderSrgbRenderingIntent.java | 15 -- .../PngEncoderVerificationUtil.java | 21 -- 17 files changed, 9 insertions(+), 1089 deletions(-) delete mode 100644 src/module-info.java delete mode 100644 src/org/atriasoft/pngencoder/PngEncoder.java delete mode 100644 src/org/atriasoft/pngencoder/PngEncoderCountingOutputStream.java delete mode 100644 src/org/atriasoft/pngencoder/PngEncoderDeflaterBuffer.java delete mode 100644 src/org/atriasoft/pngencoder/PngEncoderDeflaterBufferPool.java delete mode 100644 src/org/atriasoft/pngencoder/PngEncoderDeflaterExecutorService.java delete mode 100644 src/org/atriasoft/pngencoder/PngEncoderDeflaterExecutorServiceThreadFactory.java delete mode 100644 src/org/atriasoft/pngencoder/PngEncoderDeflaterOutputStream.java delete mode 100644 src/org/atriasoft/pngencoder/PngEncoderDeflaterSegmentResult.java delete mode 100644 src/org/atriasoft/pngencoder/PngEncoderDeflaterSegmentTask.java delete mode 100644 src/org/atriasoft/pngencoder/PngEncoderDeflaterThreadLocalDeflater.java delete mode 100644 src/org/atriasoft/pngencoder/PngEncoderIdatChunksOutputStream.java delete mode 100644 src/org/atriasoft/pngencoder/PngEncoderLogic.java delete mode 100644 src/org/atriasoft/pngencoder/PngEncoderPhysicalPixelDimensions.java delete mode 100644 src/org/atriasoft/pngencoder/PngEncoderSrgbRenderingIntent.java delete mode 100644 src/org/atriasoft/pngencoder/PngEncoderVerificationUtil.java diff --git a/pom.xml b/pom.xml index 47b3a21..7fef7fd 100644 --- a/pom.xml +++ b/pom.xml @@ -1,15 +1,9 @@ + 4.0.0 org.atriasoft png-encoder 0.1.0 - - 3.13.0 - 21 - 21 - 3.1.1 - - gitea @@ -36,7 +30,7 @@ org.junit.jupiter junit-jupiter-api - 5.11.0-M2 + 5.12.2 test @@ -48,23 +42,23 @@ - src - + src/main org.apache.maven.plugins maven-compiler-plugin - ${maven.compiler.version} + 3.14.0 - ${maven.compiler.source} - ${maven.compiler.target} - + 21 + 21 + UTF-8 org.apache.maven.plugins maven-source-plugin + 3.3.1 attach-sources @@ -78,7 +72,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.0.0-M5 + 3.5.3 maven-assembly-plugin @@ -93,30 +87,7 @@ - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.2.0 - - private - true - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.2.0 - - public - - - - diff --git a/src/module-info.java b/src/module-info.java deleted file mode 100644 index 23bb83a..0000000 --- a/src/module-info.java +++ /dev/null @@ -1,6 +0,0 @@ -module org.atriasoft.pngencoder { - exports org.atriasoft.pngencoder; - - requires transitive org.atriasoft.egami; - requires transitive org.atriasoft.etk; -} \ No newline at end of file diff --git a/src/org/atriasoft/pngencoder/PngEncoder.java b/src/org/atriasoft/pngencoder/PngEncoder.java deleted file mode 100644 index bffed89..0000000 --- a/src/org/atriasoft/pngencoder/PngEncoder.java +++ /dev/null @@ -1,173 +0,0 @@ -package org.atriasoft.pngencoder; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.zip.Deflater; - -import org.atriasoft.egami.Image; - -/** - * Main class, containing the interface for PngEncoder. - * PngEncoder is a really fast encoder for PNG images in Java. - */ -public class PngEncoder { - /** - * Compression level 9 is the default. - * It produces images with a size comparable to ImageIO. - */ - public static int DEFAULT_COMPRESSION_LEVEL = Deflater.BEST_COMPRESSION; - - private final Image bufferedImage; - private final int compressionLevel; - private final boolean multiThreadedCompressionEnabled; - private final PngEncoderPhysicalPixelDimensions physicalPixelDimensions; - private final PngEncoderSrgbRenderingIntent srgbRenderingIntent; - - /** - * Constructs an empty PngEncoder. Usually combined with methods named with*. - */ - public PngEncoder() { - this(null, PngEncoder.DEFAULT_COMPRESSION_LEVEL, true, null, null); - } - - private PngEncoder(final Image bufferedImage, final int compressionLevel, final boolean multiThreadedCompressionEnabled, final PngEncoderSrgbRenderingIntent srgbRenderingIntent, - final PngEncoderPhysicalPixelDimensions physicalPixelDimensions) { - this.bufferedImage = bufferedImage; - this.compressionLevel = PngEncoderVerificationUtil.verifyCompressionLevel(compressionLevel); - this.multiThreadedCompressionEnabled = multiThreadedCompressionEnabled; - this.srgbRenderingIntent = srgbRenderingIntent; - this.physicalPixelDimensions = physicalPixelDimensions; - } - - public Image getBufferedImage() { - return this.bufferedImage; - } - - public int getCompressionLevel() { - return this.compressionLevel; - } - - public PngEncoderSrgbRenderingIntent getSrgbRenderingIntent() { - return this.srgbRenderingIntent; - } - - public boolean isMultiThreadedCompressionEnabled() { - return this.multiThreadedCompressionEnabled; - } - - /** - * Encodes the image and returns data as {@code byte[]}. - * @throws NullPointerException if the image has not been set. - * @return encoded data - */ - public byte[] toBytes() { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(64 * 1024); - toStream(outputStream); - return outputStream.toByteArray(); - } - - /** - * Encodes the image and saves data into {@code file}. - * @param file destination file where the encoded data will be written - * @throws NullPointerException if the image has not been set. - * @throws UncheckedIOException instead of IOException - * @return number of bytes written - */ - public int toFile(final File file) { - return toFile(file.toPath()); - } - - /** - * Encodes the image and saves data into {@code filePath}. - * @param filePath destination file where the encoded data will be written - * @throws NullPointerException if the image has not been set. - * @throws UncheckedIOException instead of IOException - * @return number of bytes written - */ - public int toFile(final Path filePath) { - try (OutputStream outputStream = Files.newOutputStream(filePath)) { - return toStream(outputStream); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - /** - * Encodes the image and saves data into {@code fileName}. - * @param fileName destination file where the encoded data will be written - * @throws NullPointerException if the image has not been set. - * @throws UncheckedIOException instead of IOException - * @return number of bytes written - */ - public int toFile(final String fileName) { - return toFile(Paths.get(fileName)); - } - - /** - * Encodes the image to outputStream. - * @param outputStream destination of the encoded data - * @throws NullPointerException if the image has not been set. - * @return number of bytes written - */ - public int toStream(final OutputStream outputStream) { - try { - return PngEncoderLogic.encode(this.bufferedImage, outputStream, this.compressionLevel, this.multiThreadedCompressionEnabled, this.srgbRenderingIntent, this.physicalPixelDimensions); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - /** - * Returns a new PngEncoder which has the same configuration as this one except {@code bufferedImage}. - * The new PngEncoder will use the provided {@code bufferedImage}. - * - * @param bufferedImage input image - * @return a new PngEncoder - */ - public PngEncoder withBufferedImage(final Image bufferedImage) { - return new PngEncoder(bufferedImage, this.compressionLevel, this.multiThreadedCompressionEnabled, this.srgbRenderingIntent, this.physicalPixelDimensions); - } - - /** - * Returns a new PngEncoder which has the same configuration as this one except {@code compressionLevel}. - * The new PngEncoder will use the provided {@code compressionLevel}. - * - * @param compressionLevel input image (must be between -1 and 9 inclusive) - * @return a new PngEncoder - */ - public PngEncoder withCompressionLevel(final int compressionLevel) { - return new PngEncoder(this.bufferedImage, compressionLevel, this.multiThreadedCompressionEnabled, this.srgbRenderingIntent, this.physicalPixelDimensions); - } - - /** - * Returns a new PngEncoder which has the same configuration as this one except {@code multiThreadedCompressionEnabled}. - * The new PngEncoder will use the provided {@code multiThreadedCompressionEnabled}. - * - * @param multiThreadedCompressionEnabled when {@code true}, multithreaded compression will be used - * @return a new PngEncoder - */ - public PngEncoder withMultiThreadedCompressionEnabled(final boolean multiThreadedCompressionEnabled) { - return new PngEncoder(this.bufferedImage, this.compressionLevel, multiThreadedCompressionEnabled, this.srgbRenderingIntent, this.physicalPixelDimensions); - } - - public PngEncoder withPhysicalPixelDimensions(final PngEncoderPhysicalPixelDimensions physicalPixelDimensions) { - return new PngEncoder(this.bufferedImage, this.compressionLevel, this.multiThreadedCompressionEnabled, this.srgbRenderingIntent, physicalPixelDimensions); - } - - /** - * Returns a new PngEncoder which has the same configuration as this one except {@code srgbRenderingIntent}. - * The new PngEncoder will add an sRGB chunk to the encoded PNG and use the provided {@code srgbRenderingIntent}. - * - * @param srgbRenderingIntent the rendering intent that should be used when displaying the image - * @return a new PngEncoder - */ - public PngEncoder withSrgbRenderingIntent(final PngEncoderSrgbRenderingIntent srgbRenderingIntent) { - return new PngEncoder(this.bufferedImage, this.compressionLevel, this.multiThreadedCompressionEnabled, srgbRenderingIntent, this.physicalPixelDimensions); - } -} diff --git a/src/org/atriasoft/pngencoder/PngEncoderCountingOutputStream.java b/src/org/atriasoft/pngencoder/PngEncoderCountingOutputStream.java deleted file mode 100644 index 9d81519..0000000 --- a/src/org/atriasoft/pngencoder/PngEncoderCountingOutputStream.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.atriasoft.pngencoder; - -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Objects; - -class PngEncoderCountingOutputStream extends FilterOutputStream { - private int count; - - PngEncoderCountingOutputStream(final OutputStream out) { - super(Objects.requireNonNull(out, "out")); - } - - public int getCount() { - return this.count; - } - - @Override - public void write(final byte[] b, final int off, final int len) throws IOException { - this.out.write(b, off, len); - this.count += len; - } - - @Override - public void write(final int b) throws IOException { - this.out.write(b); - this.count++; - } -} diff --git a/src/org/atriasoft/pngencoder/PngEncoderDeflaterBuffer.java b/src/org/atriasoft/pngencoder/PngEncoderDeflaterBuffer.java deleted file mode 100644 index ed1b0ac..0000000 --- a/src/org/atriasoft/pngencoder/PngEncoderDeflaterBuffer.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.atriasoft.pngencoder; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.Objects; -import java.util.zip.Adler32; - -class PngEncoderDeflaterBuffer { - final byte[] bytes; - int length; - final PngEncoderDeflaterBufferPool pool; - - PngEncoderDeflaterBuffer(final PngEncoderDeflaterBufferPool pool, final int maxLength) { - this.pool = Objects.requireNonNull(pool, "pool"); - this.bytes = new byte[maxLength]; - this.length = 0; - } - - long calculateAdler32() { - Adler32 adler32 = new Adler32(); - adler32.update(this.bytes, 0, this.length); - return adler32.getValue(); - } - - void giveBack() { - this.pool.giveBack(this); - } - - void write(final OutputStream outputStream) throws IOException { - outputStream.write(this.bytes, 0, this.length); - } -} diff --git a/src/org/atriasoft/pngencoder/PngEncoderDeflaterBufferPool.java b/src/org/atriasoft/pngencoder/PngEncoderDeflaterBufferPool.java deleted file mode 100644 index 7a2dfdc..0000000 --- a/src/org/atriasoft/pngencoder/PngEncoderDeflaterBufferPool.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.atriasoft.pngencoder; - -import java.util.LinkedList; -import java.util.Queue; - -class PngEncoderDeflaterBufferPool { - private final int bufferMaxLength; - protected final Queue buffers; - - PngEncoderDeflaterBufferPool(final int bufferMaxLength) { - this.bufferMaxLength = bufferMaxLength; - this.buffers = new LinkedList<>(); - } - - PngEncoderDeflaterBuffer borrow() { - PngEncoderDeflaterBuffer buffer = this.buffers.poll(); - if (buffer == null) { - buffer = new PngEncoderDeflaterBuffer(this, this.bufferMaxLength); - } - return buffer; - } - - public int getBufferMaxLength() { - return this.bufferMaxLength; - } - - void giveBack(final PngEncoderDeflaterBuffer buffer) { - buffer.length = 0; - this.buffers.offer(buffer); - } - - int size() { - return this.buffers.size(); - } -} diff --git a/src/org/atriasoft/pngencoder/PngEncoderDeflaterExecutorService.java b/src/org/atriasoft/pngencoder/PngEncoderDeflaterExecutorService.java deleted file mode 100644 index 4c4d838..0000000 --- a/src/org/atriasoft/pngencoder/PngEncoderDeflaterExecutorService.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.atriasoft.pngencoder; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -class PngEncoderDeflaterExecutorService { - private static class Holder { - private static final ExecutorService INSTANCE = Executors.newFixedThreadPool(PngEncoderDeflaterExecutorService.NUM_THREADS_IS_AVAILABLE_PROCESSORS, - PngEncoderDeflaterExecutorServiceThreadFactory.getInstance()); - } - - public static int NUM_THREADS_IS_AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors(); - - static ExecutorService getInstance() { - return Holder.INSTANCE; - } - - private PngEncoderDeflaterExecutorService() {} -} diff --git a/src/org/atriasoft/pngencoder/PngEncoderDeflaterExecutorServiceThreadFactory.java b/src/org/atriasoft/pngencoder/PngEncoderDeflaterExecutorServiceThreadFactory.java deleted file mode 100644 index e99a178..0000000 --- a/src/org/atriasoft/pngencoder/PngEncoderDeflaterExecutorServiceThreadFactory.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.atriasoft.pngencoder; - -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicLong; - -class PngEncoderDeflaterExecutorServiceThreadFactory implements ThreadFactory { - private static class Holder { - private static final PngEncoderDeflaterExecutorServiceThreadFactory INSTANCE = new PngEncoderDeflaterExecutorServiceThreadFactory(); - } - - static PngEncoderDeflaterExecutorServiceThreadFactory getInstance() { - return Holder.INSTANCE; - } - - private final AtomicLong counter; - private final ThreadFactory defaultThreadFactory; - - PngEncoderDeflaterExecutorServiceThreadFactory() { - this.defaultThreadFactory = Executors.defaultThreadFactory(); - this.counter = new AtomicLong(0); - } - - @Override - public Thread newThread(final Runnable runnable) { - Thread thread = this.defaultThreadFactory.newThread(runnable); - thread.setName("PngEncoder Deflater (" + this.counter.getAndIncrement() + ")"); - thread.setDaemon(true); - return thread; - } -} diff --git a/src/org/atriasoft/pngencoder/PngEncoderDeflaterOutputStream.java b/src/org/atriasoft/pngencoder/PngEncoderDeflaterOutputStream.java deleted file mode 100644 index 2a01137..0000000 --- a/src/org/atriasoft/pngencoder/PngEncoderDeflaterOutputStream.java +++ /dev/null @@ -1,206 +0,0 @@ -package org.atriasoft.pngencoder; - -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentLinkedQueue; - -// https://tools.ietf.org/html/rfc1950 -// https://stackoverflow.com/questions/9050260/what-does-a-zlib-header-look-like -// https://www.euccas.me/zlib/ -// https://stackoverflow.com/questions/13132136/java-multithreaded-compression-with-deflater -class PngEncoderDeflaterOutputStream extends FilterOutputStream { - // The maximum amount of queued tasks. - // Multiplied because some segments compress faster than others. - // A value of 3 seems to keep all threads busy. - static final int COUNT_MAX_QUEUED_TASKS = PngEncoderDeflaterExecutorService.NUM_THREADS_IS_AVAILABLE_PROCESSORS * 3; - - // Enforces writing to underlying stream in main thread. - // Multiplied so that not all work is finished before flush to underlying stream. - static final int COUNT_MAX_TOTAL_SEGMENTS = PngEncoderDeflaterOutputStream.COUNT_MAX_QUEUED_TASKS * 3; - - // The maximum dictionary size according to the deflate specification. - // A segment max length lower than this would not allow for future use of dictionary. - // Used for unit test sanity checking. - static final int SEGMENT_MAX_LENGTH_DICTIONARY = 32 * 1024; - - // Our minimum segment length. - // Corresponds to about 2% size overhead. - // A lower value would better parallelize images but increase the size overhead. - static final int SEGMENT_MAX_LENGTH_ORIGINAL_MIN = 128 * 1024; - - static byte getFlg(final int compressionLevel) { - if (compressionLevel == -1 || compressionLevel == 6) { - return (byte) 0x9C; - } - - if (compressionLevel >= 0 && compressionLevel <= 1) { - return (byte) 0x01; - } - - if (compressionLevel >= 2 && compressionLevel <= 5) { - return (byte) 0x5E; - } - - if (compressionLevel >= 7 && compressionLevel <= 9) { - return (byte) 0xDA; - } - - throw new IllegalArgumentException("Invalid compressionLevel: " + compressionLevel); - } - - public static int getSegmentMaxLengthDeflated(final int segmentMaxLengthOriginal) { - return segmentMaxLengthOriginal + (segmentMaxLengthOriginal >> 3); - } - - public static int getSegmentMaxLengthOriginal(final int totalOriginalBytesLength) { - return Math.max(totalOriginalBytesLength / PngEncoderDeflaterOutputStream.COUNT_MAX_TOTAL_SEGMENTS, PngEncoderDeflaterOutputStream.SEGMENT_MAX_LENGTH_ORIGINAL_MIN); - } - - static void writeDeflateHeader(final OutputStream outputStream, final int compressionLevel) throws IOException { - // Write "CMF" - // " ... In practice, this means the first byte is almost always 78 (hex) ..." - outputStream.write(0x78); - - // Write "FLG" - byte flg = PngEncoderDeflaterOutputStream.getFlg(compressionLevel); - outputStream.write(flg); - } - - private long adler32; - private boolean closed; - private final int compressionLevel; - private boolean finished; - private PngEncoderDeflaterBuffer originalSegment; - private final PngEncoderDeflaterBufferPool pool; - private final ConcurrentLinkedQueue> resultQueue; - - private final int segmentMaxLengthOriginal; - - private final byte[] singleByte; - - PngEncoderDeflaterOutputStream(final OutputStream out, final int compressionLevel, final int segmentMaxLengthOriginal) throws IOException { - this(out, compressionLevel, segmentMaxLengthOriginal, new PngEncoderDeflaterBufferPool(PngEncoderDeflaterOutputStream.getSegmentMaxLengthDeflated(segmentMaxLengthOriginal))); - } - - PngEncoderDeflaterOutputStream(final OutputStream out, final int compressionLevel, final int segmentMaxLengthOriginal, final PngEncoderDeflaterBufferPool pool) throws IOException { - super(Objects.requireNonNull(out, "out")); - this.pool = Objects.requireNonNull(pool, "pool"); - this.singleByte = new byte[1]; - this.compressionLevel = compressionLevel; - this.segmentMaxLengthOriginal = segmentMaxLengthOriginal; - this.resultQueue = new ConcurrentLinkedQueue<>(); - this.originalSegment = pool.borrow(); - this.adler32 = 1; - this.finished = false; - this.closed = false; - if (pool.getBufferMaxLength() != PngEncoderDeflaterOutputStream.getSegmentMaxLengthDeflated(segmentMaxLengthOriginal)) { - throw new IllegalArgumentException("Mismatch between segmentMaxLengthOriginal and pool."); - } - PngEncoderDeflaterOutputStream.writeDeflateHeader(out, compressionLevel); - } - - @Override - public void close() throws IOException { - if (this.closed) { - return; - } - this.closed = true; - finish(); - super.close(); - } - - public void finish() throws IOException { - if (this.finished) { - return; - } - this.finished = true; - try { - submitTask(true); - joinUntilMaximumQueueSize(0); - this.out.write(ByteBuffer.allocate(4).putInt((int) this.adler32).array()); - this.out.flush(); - } finally { - this.originalSegment.giveBack(); - } - } - - void joinOne() throws IOException { - CompletableFuture resultFuture = this.resultQueue.poll(); - if (resultFuture != null) { - final PngEncoderDeflaterSegmentResult result; - try { - result = resultFuture.join(); - } catch (RuntimeException e) { - throw new IOException("An async segment task failed.", e); - } - try { - this.adler32 = result.getUpdatedAdler32(this.adler32); - result.getDeflatedSegment().write(this.out); - } finally { - result.getOriginalSegment().giveBack(); - result.getDeflatedSegment().giveBack(); - } - } - } - - void joinUntilMaximumQueueSize(final int maximumResultQueueSize) throws IOException { - while (this.resultQueue.size() > maximumResultQueueSize) { - joinOne(); - } - } - - void submitTask(final boolean lastSegment) { - final PngEncoderDeflaterBuffer deflatedSegment = this.pool.borrow(); - final PngEncoderDeflaterSegmentTask task = new PngEncoderDeflaterSegmentTask(this.originalSegment, deflatedSegment, this.compressionLevel, lastSegment); - submitTask(task); - this.originalSegment = this.pool.borrow(); - } - - void submitTask(final PngEncoderDeflaterSegmentTask task) { - CompletableFuture future = CompletableFuture.supplyAsync(task, PngEncoderDeflaterExecutorService.getInstance()); - this.resultQueue.offer(future); - } - - @Override - public void write(final byte[] b) throws IOException { - write(b, 0, b.length); - } - - @Override - public void write(final byte[] b, int off, int len) throws IOException { - if (this.finished) { - throw new IOException("write beyond end of stream"); - } - if ((off | len | (off + len) | (b.length - (off + len))) < 0) { - throw new IndexOutOfBoundsException(); - } - if (len == 0) { - return; - } - - while (len > 0) { - int freeBufCount = this.segmentMaxLengthOriginal - this.originalSegment.length; - if (freeBufCount == 0) { - // Submit task if the buffer is full and there still is more to write. - joinUntilMaximumQueueSize(PngEncoderDeflaterOutputStream.COUNT_MAX_QUEUED_TASKS - 1); - submitTask(false); - } else { - int toCopyCount = Math.min(len, freeBufCount); - System.arraycopy(b, off, this.originalSegment.bytes, this.originalSegment.length, toCopyCount); - this.originalSegment.length += toCopyCount; - off += toCopyCount; - len -= toCopyCount; - } - } - } - - @Override - public void write(final int b) throws IOException { - this.singleByte[0] = (byte) (b & 0xff); - write(this.singleByte, 0, 1); - } -} diff --git a/src/org/atriasoft/pngencoder/PngEncoderDeflaterSegmentResult.java b/src/org/atriasoft/pngencoder/PngEncoderDeflaterSegmentResult.java deleted file mode 100644 index cc40a94..0000000 --- a/src/org/atriasoft/pngencoder/PngEncoderDeflaterSegmentResult.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.atriasoft.pngencoder; - -import java.util.Objects; - -class PngEncoderDeflaterSegmentResult { - // https://github.com/madler/zlib/blob/master/adler32.c#L143 - static long combine(final long adler1, final long adler2, final long len2) { - long BASEL = 65521; - long sum1; - long sum2; - long rem; - - rem = len2 % BASEL; - sum1 = adler1 & 0xffffL; - sum2 = rem * sum1; - sum2 %= BASEL; - sum1 += (adler2 & 0xffffL) + BASEL - 1; - sum2 += ((adler1 >> 16) & 0xffffL) + ((adler2 >> 16) & 0xffffL) + BASEL - rem; - if (sum1 >= BASEL) { - sum1 -= BASEL; - } - if (sum1 >= BASEL) { - sum1 -= BASEL; - } - if (sum2 >= (BASEL << 1)) { - sum2 -= (BASEL << 1); - } - if (sum2 >= BASEL) { - sum2 -= BASEL; - } - return sum1 | (sum2 << 16); - } - - private final PngEncoderDeflaterBuffer deflatedSegment; - private final PngEncoderDeflaterBuffer originalSegment; - private final long originalSegmentAdler32; - - private final int originalSegmentLength; - - PngEncoderDeflaterSegmentResult(final PngEncoderDeflaterBuffer originalSegment, final PngEncoderDeflaterBuffer deflatedSegment, final long originalSegmentAdler32, - final int originalSegmentLength) { - this.originalSegment = Objects.requireNonNull(originalSegment, "originalSegment"); - this.deflatedSegment = Objects.requireNonNull(deflatedSegment, "deflatedSegment"); - this.originalSegmentAdler32 = originalSegmentAdler32; - this.originalSegmentLength = originalSegmentLength; - } - - public PngEncoderDeflaterBuffer getDeflatedSegment() { - return this.deflatedSegment; - } - - public PngEncoderDeflaterBuffer getOriginalSegment() { - return this.originalSegment; - } - - long getUpdatedAdler32(final long originalAdler32) { - return PngEncoderDeflaterSegmentResult.combine(originalAdler32, this.originalSegmentAdler32, this.originalSegmentLength); - } -} diff --git a/src/org/atriasoft/pngencoder/PngEncoderDeflaterSegmentTask.java b/src/org/atriasoft/pngencoder/PngEncoderDeflaterSegmentTask.java deleted file mode 100644 index 04cf6db..0000000 --- a/src/org/atriasoft/pngencoder/PngEncoderDeflaterSegmentTask.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.atriasoft.pngencoder; - -import java.util.Objects; -import java.util.function.Supplier; -import java.util.zip.Deflater; - -class PngEncoderDeflaterSegmentTask implements Supplier { - static void deflate(final PngEncoderDeflaterBuffer originalSegment, final PngEncoderDeflaterBuffer deflatedSegment, final int compressionLevel, final boolean lastSegment) { - final Deflater deflater = PngEncoderDeflaterThreadLocalDeflater.getInstance(compressionLevel); - deflater.setInput(originalSegment.bytes, 0, originalSegment.length); - - if (lastSegment) { - deflater.finish(); - } - - deflatedSegment.length = deflater.deflate(deflatedSegment.bytes, 0, deflatedSegment.bytes.length, lastSegment ? Deflater.NO_FLUSH : Deflater.SYNC_FLUSH); - } - - private final int compressionLevel; - private final PngEncoderDeflaterBuffer deflatedSegment; - private final boolean lastSegment; - - private final PngEncoderDeflaterBuffer originalSegment; - - public PngEncoderDeflaterSegmentTask(final PngEncoderDeflaterBuffer originalSegment, final PngEncoderDeflaterBuffer deflatedSegment, final int compressionLevel, final boolean lastSegment) { - this.originalSegment = Objects.requireNonNull(originalSegment, "originalSegment"); - this.deflatedSegment = Objects.requireNonNull(deflatedSegment, "deflatedSegment"); - this.compressionLevel = compressionLevel; - this.lastSegment = lastSegment; - } - - @Override - public PngEncoderDeflaterSegmentResult get() { - final long originalSegmentAdler32 = this.originalSegment.calculateAdler32(); - final int originalSegmentLength = this.originalSegment.length; - - PngEncoderDeflaterSegmentTask.deflate(this.originalSegment, this.deflatedSegment, this.compressionLevel, this.lastSegment); - - return new PngEncoderDeflaterSegmentResult(this.originalSegment, this.deflatedSegment, originalSegmentAdler32, originalSegmentLength); - } -} diff --git a/src/org/atriasoft/pngencoder/PngEncoderDeflaterThreadLocalDeflater.java b/src/org/atriasoft/pngencoder/PngEncoderDeflaterThreadLocalDeflater.java deleted file mode 100644 index f795bdc..0000000 --- a/src/org/atriasoft/pngencoder/PngEncoderDeflaterThreadLocalDeflater.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.atriasoft.pngencoder; - -import java.util.zip.Deflater; - -/** - * We save time by allocating and reusing some thread local state. - * - * Creating a new Deflater instance takes a surprising amount of time. - * Resetting an existing Deflater instance is almost free though. - */ -class PngEncoderDeflaterThreadLocalDeflater { - private static final ThreadLocal THREAD_LOCAL = ThreadLocal.withInitial(PngEncoderDeflaterThreadLocalDeflater::new); - - static Deflater getInstance(final int compressionLevel) { - return PngEncoderDeflaterThreadLocalDeflater.THREAD_LOCAL.get().getDeflater(compressionLevel); - } - - private final Deflater[] deflaters; - - private PngEncoderDeflaterThreadLocalDeflater() { - this.deflaters = new Deflater[11]; - for (int compressionLevel = -1; compressionLevel <= 9; compressionLevel++) { - boolean nowrap = true; - this.deflaters[compressionLevel + 1] = new Deflater(compressionLevel, nowrap); - } - } - - private Deflater getDeflater(final int compressionLevel) { - Deflater deflater = this.deflaters[compressionLevel + 1]; - deflater.reset(); - return deflater; - } -} diff --git a/src/org/atriasoft/pngencoder/PngEncoderIdatChunksOutputStream.java b/src/org/atriasoft/pngencoder/PngEncoderIdatChunksOutputStream.java deleted file mode 100644 index 817bb4b..0000000 --- a/src/org/atriasoft/pngencoder/PngEncoderIdatChunksOutputStream.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.atriasoft.pngencoder; - -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.util.zip.CRC32; - -class PngEncoderIdatChunksOutputStream extends FilterOutputStream { - // An IDAT chunk adds 12 bytes of overhead to the data within. - // 12 / (32 * 1024) = 0.00037 meaning the size overhead is just 0.037% which should be negligible. - static final int DEFAULT_BUFFER_LENGTH = 32 * 1024; - - static final byte[] IDAT_BYTES = "IDAT".getBytes(StandardCharsets.US_ASCII); - - private final byte[] buf; - private int count; - private final CRC32 crc; - - PngEncoderIdatChunksOutputStream(final OutputStream out) { - this(out, PngEncoderIdatChunksOutputStream.DEFAULT_BUFFER_LENGTH); - } - - PngEncoderIdatChunksOutputStream(final OutputStream out, final int bufferLength) { - super(out); - this.crc = new CRC32(); - this.buf = new byte[bufferLength]; - this.count = 0; - } - - @Override - public void flush() throws IOException { - flushBuffer(); - super.flush(); - } - - private void flushBuffer() throws IOException { - if (this.count > 0) { - writeIdatChunk(this.buf, 0, this.count); - this.count = 0; - } - } - - @Override - public void write(final byte[] b) throws IOException { - write(b, 0, b.length); - } - - @Override - public void write(final byte[] b, final int off, final int len) throws IOException { - if (len >= this.buf.length) { - flushBuffer(); - writeIdatChunk(b, off, len); - return; - } - if (len > this.buf.length - this.count) { - flushBuffer(); - } - System.arraycopy(b, off, this.buf, this.count, len); - this.count += len; - } - - @Override - public void write(final int b) throws IOException { - if (this.count >= this.buf.length) { - flushBuffer(); - } - this.buf[this.count++] = (byte) b; - } - - private void writeIdatChunk(final byte[] b, final int off, final int len) throws IOException { - writeInt(len); - this.out.write(PngEncoderIdatChunksOutputStream.IDAT_BYTES); - this.out.write(b, off, len); - this.crc.reset(); - this.crc.update(PngEncoderIdatChunksOutputStream.IDAT_BYTES); - this.crc.update(b, off, len); - writeInt((int) this.crc.getValue()); - } - - private void writeInt(final int i) throws IOException { - this.out.write((byte) (i >> 24) & 0xFF); - this.out.write((byte) (i >> 16) & 0xFF); - this.out.write((byte) (i >> 8) & 0xFF); - this.out.write((byte) i & 0xFF); - } -} diff --git a/src/org/atriasoft/pngencoder/PngEncoderLogic.java b/src/org/atriasoft/pngencoder/PngEncoderLogic.java deleted file mode 100644 index 3de5403..0000000 --- a/src/org/atriasoft/pngencoder/PngEncoderLogic.java +++ /dev/null @@ -1,140 +0,0 @@ -package org.atriasoft.pngencoder; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.Objects; -import java.util.zip.CRC32; -import java.util.zip.Deflater; -import java.util.zip.DeflaterOutputStream; - -import org.atriasoft.egami.ToolImage; -import org.atriasoft.egami.Image; - -class PngEncoderLogic { - public static final byte[] CHRM_SRGB_VALUE = ByteBuffer.allocate(8 * 4).putInt(31270).putInt(32900).putInt(64000).putInt(33000).putInt(30000).putInt(60000).putInt(15000).putInt(6000).array(); - - // In hex: 89 50 4E 47 0D 0A 1A 0A - // This is the "file beginning" aka "header" aka "signature" aka "magicnumber". - // https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_header - // http://www.libpng.org/pub/png/book/chapter08.html#png.ch08.div.2 - // All PNGs start this way and it does not include any pixel format info. - static final byte[] FILE_BEGINNING = { -119, 80, 78, 71, 13, 10, 26, 10 }; - - // In hex: 00 00 00 00 49 45 4E 44 AE 42 60 82 - // This is the "file ending" - static final byte[] FILE_ENDING = { 0, 0, 0, 0, 73, 69, 78, 68, -82, 66, 96, -126 }; - // Default values for the gAMA and cHRM chunks when an sRGB chunk is used, - // as specified at http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.sRGB - // "An application that writes the sRGB chunk should also write a gAMA chunk (and perhaps a cHRM chunk) - // for compatibility with applications that do not use the sRGB chunk. - // In this situation, only the following values may be used: - // ..." - public static final byte[] GAMA_SRGB_VALUE = ByteBuffer.allocate(4).putInt(45455).array(); - static final byte IHDR_BIT_DEPTH = 8; - static final byte IHDR_COLOR_TYPE_RGB = 2; - static final byte IHDR_COLOR_TYPE_RGBA = 6; - static final byte IHDR_COMPRESSION_METHOD = 0; - - static final byte IHDR_FILTER_METHOD = 0; - static final byte IHDR_INTERLACE_METHOD = 0; - - static byte[] asChunk(final String type, final byte[] data) { - PngEncoderVerificationUtil.verifyChunkType(type); - ByteBuffer byteBuffer = ByteBuffer.allocate(data.length + 12); - byteBuffer.putInt(data.length); - ByteBuffer byteBufferForCrc = byteBuffer.slice().asReadOnlyBuffer(); - byteBufferForCrc.limit(4 + data.length); - byteBuffer.put(type.getBytes(StandardCharsets.US_ASCII)); - byteBuffer.put(data); - byteBuffer.putInt(PngEncoderLogic.getCrc32(byteBufferForCrc)); - return byteBuffer.array(); - } - - static int encode(final Image bufferedImage, final OutputStream outputStream, final int compressionLevel, final boolean multiThreadedCompressionEnabled, - final PngEncoderSrgbRenderingIntent srgbRenderingIntent, final PngEncoderPhysicalPixelDimensions physicalPixelDimensions) throws IOException { - Objects.requireNonNull(bufferedImage, "bufferedImage"); - Objects.requireNonNull(outputStream, "outputStream"); - - final boolean alpha = bufferedImage.hasAlpha(); - final int width = bufferedImage.getWidth(); - final int height = bufferedImage.getHeight(); - final PngEncoderCountingOutputStream countingOutputStream = new PngEncoderCountingOutputStream(outputStream); - - countingOutputStream.write(PngEncoderLogic.FILE_BEGINNING); - - final byte[] ihdr = PngEncoderLogic.getIhdrHeader(width, height, alpha); - final byte[] ihdrChunk = PngEncoderLogic.asChunk("IHDR", ihdr); - countingOutputStream.write(ihdrChunk); - - if (srgbRenderingIntent != null) { - outputStream.write(PngEncoderLogic.asChunk("sRGB", new byte[] { srgbRenderingIntent.getValue() })); - outputStream.write(PngEncoderLogic.asChunk("gAMA", PngEncoderLogic.GAMA_SRGB_VALUE)); - outputStream.write(PngEncoderLogic.asChunk("cHRM", PngEncoderLogic.CHRM_SRGB_VALUE)); - } - - if (physicalPixelDimensions != null) { - outputStream.write(PngEncoderLogic.asChunk("pHYs", PngEncoderLogic.getPhysicalPixelDimensions(physicalPixelDimensions))); - } - - PngEncoderIdatChunksOutputStream idatChunksOutputStream = new PngEncoderIdatChunksOutputStream(countingOutputStream); - final byte[] scanlineBytes; - - if (bufferedImage.hasAlpha()) { - scanlineBytes = ToolImage.pngConvertInByteBufferRGBA(bufferedImage); - } else { - scanlineBytes = ToolImage.pngConvertInByteBufferRGB(bufferedImage); - } - - final int segmentMaxLengthOriginal = PngEncoderDeflaterOutputStream.getSegmentMaxLengthOriginal(scanlineBytes.length); - - if (scanlineBytes.length <= segmentMaxLengthOriginal || !multiThreadedCompressionEnabled) { - Deflater deflater = new Deflater(compressionLevel); - DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(idatChunksOutputStream, deflater); - deflaterOutputStream.write(scanlineBytes); - deflaterOutputStream.finish(); - deflaterOutputStream.flush(); - //deflaterOutputStream.close(); - } else { - PngEncoderDeflaterOutputStream deflaterOutputStream = new PngEncoderDeflaterOutputStream(idatChunksOutputStream, compressionLevel, segmentMaxLengthOriginal); - deflaterOutputStream.write(scanlineBytes); - deflaterOutputStream.finish(); - //deflaterOutputStream.close(); - } - - countingOutputStream.write(PngEncoderLogic.FILE_ENDING); - - countingOutputStream.flush(); - - return countingOutputStream.getCount(); - } - - static int getCrc32(final ByteBuffer byteBuffer) { - CRC32 crc = new CRC32(); - crc.update(byteBuffer); - return (int) crc.getValue(); - } - - static byte[] getIhdrHeader(final int width, final int height, final boolean alpha) { - ByteBuffer buffer = ByteBuffer.allocate(13); - buffer.putInt(width); - buffer.putInt(height); - buffer.put(PngEncoderLogic.IHDR_BIT_DEPTH); - buffer.put(alpha ? PngEncoderLogic.IHDR_COLOR_TYPE_RGBA : PngEncoderLogic.IHDR_COLOR_TYPE_RGB); - buffer.put(PngEncoderLogic.IHDR_COMPRESSION_METHOD); - buffer.put(PngEncoderLogic.IHDR_FILTER_METHOD); - buffer.put(PngEncoderLogic.IHDR_INTERLACE_METHOD); - return buffer.array(); - } - - static byte[] getPhysicalPixelDimensions(final PngEncoderPhysicalPixelDimensions physicalPixelDimensions) { - ByteBuffer buffer = ByteBuffer.allocate(9); - buffer.putInt(physicalPixelDimensions.getPixelsPerUnitX()); - buffer.putInt(physicalPixelDimensions.getPixelsPerUnitY()); - buffer.put(physicalPixelDimensions.getUnit().getValue()); - return buffer.array(); - } - - private PngEncoderLogic() {} -} diff --git a/src/org/atriasoft/pngencoder/PngEncoderPhysicalPixelDimensions.java b/src/org/atriasoft/pngencoder/PngEncoderPhysicalPixelDimensions.java deleted file mode 100644 index 97f4157..0000000 --- a/src/org/atriasoft/pngencoder/PngEncoderPhysicalPixelDimensions.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.atriasoft.pngencoder; - -/** - * Represents PNG physical pixel dimensions - * - * Use one of the static methods to create physical pixel dimensions based - * on pixels per meter, dots per inch or a unit-less aspect ratio. - * - * @see https://www.w3.org/TR/PNG/#11pHYs - */ -public class PngEncoderPhysicalPixelDimensions { - - public enum Unit { - METER((byte) 1), UNKNOWN((byte) 0); - - private final byte value; - - Unit(final byte value) { - this.value = value; - } - - public byte getValue() { - return this.value; - } - } - - private static final float INCHES_PER_METER = 100 / 2.54f; - - /** - * Creates a PngEncoderPhysicalPixelDimensions that only specifies the aspect ratio, - * but not the size, of the pixels - * - * @param pixelsPerUnitX the number of pixels per unit in the horizontal dimension - * @param pixelsPerUnitY the number of pixels per unit in the vertical dimension - */ - public static PngEncoderPhysicalPixelDimensions aspectRatio(final int pixelsPerUnitX, final int pixelsPerUnitY) { - return new PngEncoderPhysicalPixelDimensions(pixelsPerUnitX, pixelsPerUnitY, Unit.UNKNOWN); - } - - /** - * Creates a PngEncoderPhysicalPixelDimensions with square pixels - * with a size specified in dots per inch - * - * Note that dots per inch (DPI) cannot be exactly represented by the PNG format's - * integer value for pixels per meter. There will be a slight rounding error. - * - * @param dotsPerInch the DPI value for both dimensions - */ - public static PngEncoderPhysicalPixelDimensions dotsPerInch(final int dotsPerInch) { - return PngEncoderPhysicalPixelDimensions.dotsPerInch(dotsPerInch, dotsPerInch); - } - - /** - * Creates a PngEncoderPhysicalPixelDimensions with possibly non-square pixels - * with a size specified in dots per inch - * - * Note that dots per inch (DPI) cannot be exactly represented by the PNG format's - * integer value for pixels per meter. There will be a slight rounding error. - * - * @param dotsPerInchX the DPI value for the horizontal dimension - * @param dotsPerInchY the DPI value for the vertical dimension - */ - public static PngEncoderPhysicalPixelDimensions dotsPerInch(final int dotsPerInchX, final int dotsPerInchY) { - int pixelsPerMeterX = Math.round(dotsPerInchX * PngEncoderPhysicalPixelDimensions.INCHES_PER_METER); - int pixelsPerMeterY = Math.round(dotsPerInchY * PngEncoderPhysicalPixelDimensions.INCHES_PER_METER); - - return new PngEncoderPhysicalPixelDimensions(pixelsPerMeterX, pixelsPerMeterY, Unit.METER); - } - - /** - * Creates a PngEncoderPhysicalPixelDimensions with square pixels - * with a size specified in pixels per meter - * - * @param pixelsPerMeter the pixels per meter value for both dimensions - */ - public static PngEncoderPhysicalPixelDimensions pixelsPerMeter(final int pixelsPerMeter) { - return PngEncoderPhysicalPixelDimensions.pixelsPerMeter(pixelsPerMeter, pixelsPerMeter); - } - - /** - * Creates a PngEncoderPhysicalPixelDimensions with possibly non-square pixels - * with a size specified in pixels per meter - * - * @param pixelsPerMeterX the pixels per meter value for the horizontal dimension - * @param pixelsPerMeterY the pixels per meter value for the vertical dimension - */ - public static PngEncoderPhysicalPixelDimensions pixelsPerMeter(final int pixelsPerMeterX, final int pixelsPerMeterY) { - return new PngEncoderPhysicalPixelDimensions(pixelsPerMeterX, pixelsPerMeterY, Unit.METER); - } - - private final int pixelsPerUnitX; - - private final int pixelsPerUnitY; - - private final Unit unit; - - private PngEncoderPhysicalPixelDimensions(final int pixelsPerUnitX, final int pixelsPerUnitY, final Unit unit) { - this.pixelsPerUnitX = pixelsPerUnitX; - this.pixelsPerUnitY = pixelsPerUnitY; - this.unit = unit; - } - - /** - * @return the number of pixels per unit in the horizontal dimension - */ - public int getPixelsPerUnitX() { - return this.pixelsPerUnitX; - } - - /** - * @return the number of pixels per unit in the vertical dimension - */ - public int getPixelsPerUnitY() { - return this.pixelsPerUnitY; - } - - /** - * @return the unit of the pixel size (either {@link Unit#METER} or {@link Unit#UNKNOWN}) - */ - public Unit getUnit() { - return this.unit; - } -} diff --git a/src/org/atriasoft/pngencoder/PngEncoderSrgbRenderingIntent.java b/src/org/atriasoft/pngencoder/PngEncoderSrgbRenderingIntent.java deleted file mode 100644 index 1340972..0000000 --- a/src/org/atriasoft/pngencoder/PngEncoderSrgbRenderingIntent.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.atriasoft.pngencoder; - -public enum PngEncoderSrgbRenderingIntent { - ABSOLUTE_COLORIMETRIC((byte) 3), PERCEPTUAL((byte) 0), RELATIVE_COLORIMETRIC((byte) 1), SATURATION((byte) 2); - - private final byte value; - - PngEncoderSrgbRenderingIntent(final byte value) { - this.value = value; - } - - public byte getValue() { - return this.value; - } -} diff --git a/src/org/atriasoft/pngencoder/PngEncoderVerificationUtil.java b/src/org/atriasoft/pngencoder/PngEncoderVerificationUtil.java deleted file mode 100644 index 4981ec6..0000000 --- a/src/org/atriasoft/pngencoder/PngEncoderVerificationUtil.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.atriasoft.pngencoder; - -class PngEncoderVerificationUtil { - static String verifyChunkType(final String chunkType) { - if (chunkType.length() != 4) { - String message = String.format("The chunkType must be four letters, but was \"%s\". See http://www.libpng.org/pub/png/book/chapter08.html#png.ch08.div.1", chunkType); - throw new IllegalArgumentException(message); - } - return chunkType; - } - - static int verifyCompressionLevel(final int compressionLevel) { - if ((compressionLevel < -1) || (compressionLevel > 9)) { - String message = String.format("The compressionLevel must be between -1 and 9 inclusive, but was %d.", compressionLevel); - throw new IllegalArgumentException(message); - } - return compressionLevel; - } - - private PngEncoderVerificationUtil() {} -}