Compare commits

..

9 Commits

Author SHA1 Message Date
Harald Kuhr 1dc6045079 Sonar issues 2026-03-11 19:08:53 +01:00
Harald Kuhr b9b3c364be Added raster write test
+ fixed a small issue for PAM
2026-03-11 18:53:24 +01:00
Harald Kuhr 26ecf18c68 Mipmap support using ImageIO sequence API 2026-03-11 18:26:02 +01:00
Harald Kuhr 3f356a8197 More clean-up: Now keeps stream byte order consistent (LE), support for Raster, more tests 2026-03-11 15:59:48 +01:00
Harald Kuhr dc59c66209 More clean-up: Removed optional flags from param, header size validation, metadata now reports compresion as lossy 2026-03-11 14:51:01 +01:00
Harald Kuhr 2a0b15f33f Sonar issues + roll back accidental check-in 2026-03-10 23:02:47 +01:00
Harald Kuhr 9ca9569537 Fix JavaDoc 🎉 2026-03-10 22:42:39 +01:00
Harald Kuhr 0a717ea0af Major rework/standardization:
* DDSEncoderType, DX10DXGIFormat merged with DDSType for a single way to describe a DDS format
 * Added constants for DXGI formats
 * DDSImageWriteParam is now mutable and supports standard way of setting compression type
 * DDSImageMetadata now supports more of the format
 Performance:
 * DDSReader now use seek() to jump to correct mipmap instead of reading all bytes
 * DDSImageWriter now uses getTile(0, 0) instead of getData() for better performance
2026-03-10 22:38:58 +01:00
Harald Kuhr 7f2eb517ef Refactorings and code clean-up 2026-03-05 21:02:04 +01:00
13 changed files with 124 additions and 281 deletions
+3 -3
View File
@@ -35,7 +35,7 @@ jobs:
- name: Run Tests
run: mvn --batch-mode --no-transfer-progress test
- name: Publish Test Report
uses: mikepenz/action-junit-report@49b2ca06f62aa7ef83ae6769a2179271e160d8e4 # v5
uses: mikepenz/action-junit-report@74626db7353a25a20a72816467ebf035f674c5f8 # v5
if: ${{ !cancelled() }}
with:
report_paths: "**/target/surefire-reports/TEST*.xml"
@@ -57,7 +57,7 @@ jobs:
- name: Run Tests
run: mvn --batch-mode --no-transfer-progress test
- name: Publish Test Report
uses: mikepenz/action-junit-report@49b2ca06f62aa7ef83ae6769a2179271e160d8e4 # v5
uses: mikepenz/action-junit-report@74626db7353a25a20a72816467ebf035f674c5f8 # v5
if: ${{ !cancelled() }}
with:
report_paths: "**/target/surefire-reports/TEST*.xml"
@@ -91,7 +91,7 @@ jobs:
- name: Run Tests
run: mvn --batch-mode --no-transfer-progress test
- name: Publish Test Report
uses: mikepenz/action-junit-report@49b2ca06f62aa7ef83ae6769a2179271e160d8e4 # v5
uses: mikepenz/action-junit-report@74626db7353a25a20a72816467ebf035f674c5f8 # v5
if: ${{ !cancelled() }}
with:
report_paths: "**/target/surefire-reports/TEST*.xml"
+1 -1
View File
@@ -49,7 +49,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: SARIF file
path: results.sarif
@@ -32,7 +32,6 @@ package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import com.twelvemonkeys.imageio.util.SequenceSupport;
import javax.imageio.IIOException;
import javax.imageio.IIOImage;
@@ -65,7 +64,7 @@ public final class ICOImageWriter extends DIBImageWriter {
private static final int ICO_MAX_DIMENSION = 256;
private static final int INITIAL_ENTRY_COUNT = 8;
private final SequenceSupport sequence = new SequenceSupport();
private int sequenceIndex = -1;
private ImageWriter pngDelegate;
@@ -75,7 +74,7 @@ public final class ICOImageWriter extends DIBImageWriter {
@Override
protected void resetMembers() {
sequence.reset();
sequenceIndex = -1;
if (pngDelegate != null) {
pngDelegate.dispose();
@@ -108,12 +107,16 @@ public final class ICOImageWriter extends DIBImageWriter {
@Override
public void prepareWriteSequence(final IIOMetadata streamMetadata) throws IOException {
assertOutput();
sequence.start();
if (sequenceIndex >= 0) {
throw new IllegalStateException("writeSequence already started");
}
writeICOHeader();
// Count: Needs to be updated for each new image
imageOutput.writeShort(0);
sequenceIndex = 0;
// TODO: Allow passing the initial size of the directory in the stream metadata?
// - as this is much more efficient than growing...
@@ -127,19 +130,27 @@ public final class ICOImageWriter extends DIBImageWriter {
@Override
public void endWriteSequence() {
assertOutput();
sequence.end();
if (sequenceIndex < 0) {
throw new IllegalStateException("prepareWriteSequence not called");
}
sequenceIndex = -1;
}
@Override
public void writeToSequence(final IIOImage image, final ImageWriteParam param) throws IOException {
assertOutput();
int imageIndex = sequence.advance();
if (sequenceIndex < 0) {
throw new IllegalStateException("prepareWriteSequence not called");
}
if (image.hasRaster()) {
throw new UnsupportedOperationException("Raster not supported");
}
if (imageIndex >= INITIAL_ENTRY_COUNT) {
if (sequenceIndex >= INITIAL_ENTRY_COUNT) {
growIfNecessary();
}
@@ -161,7 +172,7 @@ public final class ICOImageWriter extends DIBImageWriter {
// Uncompressed, RLE4/RLE8 or PNG compressed
boolean pngCompression = param != null && "BI_PNG".equals(param.getCompressionType());
processImageStarted(imageIndex);
processImageStarted(sequenceIndex);
if (pngCompression) {
// NOTE: Embedding a PNG in a ICO is slightly different than a BMP with BI_PNG compression,
@@ -187,15 +198,17 @@ public final class ICOImageWriter extends DIBImageWriter {
// Update count
imageOutput.seek(4);
imageOutput.writeShort(imageIndex + 1);
imageOutput.writeShort(sequenceIndex + 1);
// Write entry
int entryPosition = 6 + imageIndex * ENTRY_SIZE;
int entryPosition = 6 + sequenceIndex * ENTRY_SIZE;
imageOutput.seek(entryPosition);
long size = nextPosition - imageOffset;
writeEntry(width, height, colorModel, (int) size, (int) imageOffset);
sequenceIndex++;
imageOutput.seek(nextPosition);
}
@@ -252,7 +265,7 @@ public final class ICOImageWriter extends DIBImageWriter {
pngDelegate.addIIOWriteWarningListener(new IIOWriteWarningListener() {
@Override
public void warningOccurred(ImageWriter source, int imageIndex, String warning) {
processWarningOccurred(sequence.current(), warning);
processWarningOccurred(sequenceIndex, warning);
}
});
}
@@ -1,115 +0,0 @@
/*
* Copyright (c) 2026, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.util;
import javax.imageio.IIOImage;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
/**
* A tiny utility class that keeps state for sequences.
* For use by {@code ImageWriter} implementations that supports sequence (multiple images in same stream).
*
* @see ImageWriter#canWriteSequence()
*/
public final class SequenceSupport {
// Initial state, no sequence running
private int index = -1;
/**
* Resets the sequence to initial state, regardless of the current sequence state.
*/
public void reset() {
index = -1;
}
/**
* Starts a new sequence.
*
* @throws IllegalStateException if a sequence is already running.
* @see ImageWriter#prepareWriteSequence(IIOMetadata)
*/
public void start() {
if (index >= 0) {
throw new IllegalStateException("prepareWriteSequence already invoked");
}
index = 0;
}
/**
* Advances the current sequence.
*
* @return the current sequence index.
* @throws IllegalStateException if a sequence is not running.
* @see ImageWriter#writeToSequence(IIOImage, ImageWriteParam)
*/
public int advance() {
if (index < 0) {
throw new IllegalStateException("prepareWriteSequence not invoked");
}
return index++;
}
/**
* Gets the current sequence index.
*
* @return the current sequence index, or {@code -1} if a sequence is not running.
*/
public int current() {
// This method does not throw IllegalStateException, to allow
// ImageWriters to use the index in cases that may or may not
// happen "inside" a sequence.
// I'm not entirely sure if this is a good idea...
return index;
}
/**
* Ends the current sequence.
* The sequence is reset to initial state, and a new sequence may be started.
*
* @return the current (last) sequence index
* @throws IllegalStateException if a sequence is not running.
* @see ImageWriter#endWriteSequence()
*/
public int end() {
if (index < 0) {
throw new IllegalStateException("prepareWriteSequence not invoked");
}
int last = index;
index = -1;
return last;
}
}
@@ -1,89 +0,0 @@
package com.twelvemonkeys.imageio.util;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class SequenceSupportTest {
@Test
void happyCase() {
SequenceSupport sequence = new SequenceSupport();
sequence.start();
assertEquals(0, sequence.current());
for (int i = 0; i < Byte.MAX_VALUE; i++) {
assertEquals(i, sequence.advance());
assertEquals(i + 1, sequence.current());
}
assertEquals(127, sequence.end());
assertEquals(-1, sequence.current());
assertThrows(IllegalStateException.class, sequence::advance);
}
@Test
void reset() {
SequenceSupport sequence = new SequenceSupport();
sequence.reset();
assertEquals(-1, sequence.current());
assertThrows(IllegalStateException.class, sequence::end);
sequence.start();
sequence.reset();
assertEquals(-1, sequence.current());
assertThrows(IllegalStateException.class, sequence::end);
sequence.start();
sequence.advance();
sequence.reset();
assertEquals(-1, sequence.current());
assertThrows(IllegalStateException.class, sequence::end);
sequence.start();
sequence.end();
sequence.reset();
assertEquals(-1, sequence.current());
assertThrows(IllegalStateException.class, sequence::end);
}
@Test
void startEnd() {
SequenceSupport sequence = new SequenceSupport();
sequence.start();
sequence.end();
assertEquals(-1, sequence.current());
assertThrows(IllegalStateException.class, sequence::end);
}
@Test
void startAlreadyStarted() {
SequenceSupport sequence = new SequenceSupport();
sequence.start();
assertThrows(IllegalStateException.class, sequence::start);
}
@Test
void advanceNotStarted() {
SequenceSupport sequence = new SequenceSupport();
assertThrows(IllegalStateException.class, sequence::advance);
}
@Test
void currentNotStarted() {
SequenceSupport sequence = new SequenceSupport();
assertEquals(-1, sequence.current());
}
@Test
void endNotStarted() {
SequenceSupport sequence = new SequenceSupport();
assertThrows(IllegalStateException.class, sequence::end);
}
}
@@ -47,12 +47,9 @@ import java.awt.Dimension;
import java.io.IOException;
import java.util.Arrays;
/**
* @see <a href="https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-header">DDS_HEADER structure</a>
* @see <a href="https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-pguide">Programming Guide for DDS</a>
*/
final class DDSHeader {
// https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-pguide
private int flags;
private int mipMapCount;
@@ -72,16 +69,26 @@ final class DDSHeader {
static DDSHeader read(final ImageInputStream imageInput) throws IOException {
DDSHeader header = new DDSHeader();
// Read MAGIC bytes [0,3]
int magic = imageInput.readInt();
if (magic != DDS.MAGIC) {
throw new IIOException(String.format("Not a DDS file. Expected DDS magic 0x%8x', read 0x%8x", DDS.MAGIC, magic));
}
// DDS_HEADER structure
// https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-header
int dwSize = imageInput.readInt(); // [4,7]
if (dwSize != DDS.HEADER_SIZE) {
throw new IIOException(String.format("Invalid DDS header size (expected %d): %d", DDS.HEADER_SIZE, dwSize));
}
// Verify flags
// Verify setFlags
header.flags = imageInput.readInt(); // [8,11]
if (!header.hasFlag(DDS.FLAG_CAPS | DDS.FLAG_HEIGHT | DDS.FLAG_WIDTH | DDS.FLAG_PIXELFORMAT)) {
// NOTE: The Microsoft DDS documentation mention that readers should not rely on these flags...
throw new IIOException("Required DDS flag missing in header: " + Integer.toBinaryString(header.flags));
if (!header.getFlag(DDS.FLAG_CAPS
| DDS.FLAG_HEIGHT
| DDS.FLAG_WIDTH
| DDS.FLAG_PIXELFORMAT)) {
throw new IIOException("Required DDS Flag missing in header: " + Integer.toBinaryString(header.flags));
}
// Read Height & Width
@@ -102,7 +109,7 @@ final class DDSHeader {
// DDS_PIXELFORMAT structure
int px_dwSize = imageInput.readInt(); // [76,79]
if (px_dwSize != DDS.PIXELFORMAT_SIZE) {
throw new IIOException(String.format("Invalid DDS pixel format structure size (expected %d): %d", DDS.PIXELFORMAT_SIZE, dwSize));
throw new IIOException(String.format("Invalid DDS PIXELFORMAT size (expected %d): %d", DDS.PIXELFORMAT_SIZE, dwSize));
}
header.pixelFormatFlags = imageInput.readInt(); // [80,83]
@@ -121,7 +128,6 @@ final class DDSHeader {
int dwReserved2 = imageInput.readInt(); // [124,127]
if (header.fourCC == DDSType.DXT10.fourCC()) {
// If DXT10, the DXT10 header will follow immediately
header.dxt10Header = DXT10Header.read(imageInput);
}
@@ -140,8 +146,8 @@ final class DDSHeader {
}
}
private boolean hasFlag(int mask) {
return (flags & mask) == mask;
private boolean getFlag(int mask) {
return (flags & mask) != 0;
}
int getWidth(int imageIndex) {
@@ -77,4 +77,5 @@ final class DDSImageMetadata extends StandardImageMetadataSupport {
return count;
}
}
@@ -33,7 +33,6 @@ package com.twelvemonkeys.imageio.plugins.dds;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
@@ -168,12 +167,6 @@ public final class DDSImageReader extends ImageReaderBase {
private void readHeader() throws IOException {
if (header == null) {
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
int magic = imageInput.readInt();
if (magic != DDS.MAGIC) {
throw new IIOException(String.format("Not a DDS file. Expected DDS magic 0x%8x', read 0x%8x", DDS.MAGIC, magic));
}
header = DDSHeader.read(imageInput);
imageInput.flushBefore(imageInput.getStreamPosition());
}
@@ -2,7 +2,6 @@ package com.twelvemonkeys.imageio.plugins.dds;
import com.twelvemonkeys.imageio.ImageWriterBase;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.SequenceSupport;
import javax.imageio.IIOException;
import javax.imageio.IIOImage;
@@ -30,9 +29,9 @@ import java.nio.file.Paths;
*/
class DDSImageWriter extends ImageWriterBase {
private final SequenceSupport mipmapSequence = new SequenceSupport();
private long headerStartPos;
private long startPos;
// TODO: Create a SequenceSupport class that handles sequence prepare/write/end
private int mipmapIndex = -1;
private DDSType mipmapType;
private Dimension mipmapDimension;
@@ -47,8 +46,7 @@ class DDSImageWriter extends ImageWriterBase {
@Override
protected void resetMembers() {
headerStartPos = 0;
mipmapSequence.reset();
mipmapIndex = -1;
mipmapType = null;
mipmapDimension = null;
}
@@ -66,22 +64,30 @@ class DDSImageWriter extends ImageWriterBase {
@Override
public void prepareWriteSequence(IIOMetadata streamMetadata) throws IOException {
assertOutput();
mipmapSequence.start();
if (mipmapIndex >= 0) {
throw new IllegalStateException("writeSequence already started");
}
mipmapIndex = 0;
startPos = imageOutput.getStreamPosition();
imageOutput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
imageOutput.writeInt(DDS.MAGIC);
imageOutput.flush();
headerStartPos = imageOutput.getStreamPosition();
}
@Override
public void endWriteSequence() throws IOException {
int mipmapCount = mipmapSequence.end();
assertOutput();
// Go back and update header
updateHeader(mipmapCount);
if (mipmapIndex < 0) {
throw new IllegalStateException("prepareWriteSequence not called");
}
// Go back and update hader
updateHeader(mipmapIndex);
mipmapIndex = -1;
mipmapType = null;
mipmapDimension = null;
@@ -97,12 +103,13 @@ class DDSImageWriter extends ImageWriterBase {
@Override
public void writeToSequence(IIOImage image, ImageWriteParam param) throws IOException {
int mipmapIndex = mipmapSequence.advance();
if (mipmapIndex < 0) {
throw new IllegalStateException("prepareWriteSequence not called");
}
Raster raster = getRaster(image);
ensureImageChannels(raster);
ensureTextureDimension(raster);
mipmapDimension = new Dimension(raster.getWidth(), raster.getHeight());
DDSImageWriteParam ddsParam = param instanceof DDSImageWriteParam
? ((DDSImageWriteParam) param)
@@ -113,7 +120,7 @@ class DDSImageWriter extends ImageWriterBase {
mipmapType = type;
}
else if (type != mipmapType) {
processWarningOccurred(mipmapIndex, "All images in DDS mipmap must use same pixel format and compression");
processWarningOccurred(mipmapIndex, "All images in DDS MipMap must use same pixel format and compression");
}
if (mipmapType == null) {
throw new IIOException("Only compressed DDS using DXT1-5 or DXT10 with block compression is currently supported");
@@ -133,6 +140,9 @@ class DDSImageWriter extends ImageWriterBase {
processImageProgress(100f);
processImageComplete();
mipmapDimension = new Dimension(raster.getWidth(), raster.getHeight());
mipmapIndex++;
}
private static Raster getRaster(IIOImage image) throws IIOException {
@@ -200,7 +210,7 @@ class DDSImageWriter extends ImageWriterBase {
//dwDepth
imageOutput.writeInt(0);
//dwMipmapCount
imageOutput.writeInt(1); // Should probably write 0 here for non-mipmap?
imageOutput.writeInt(1);
//reserved
imageOutput.write(new byte[44]);
//pixFmt
@@ -220,7 +230,7 @@ class DDSImageWriter extends ImageWriterBase {
}
long streamPosition = imageOutput.getStreamPosition();
imageOutput.seek(headerStartPos + 4); // Seek back to header start, skip 4 byte header size
imageOutput.seek(startPos + 8); // Seek back to start + 4 magic + 4 header size
int flags = imageOutput.readInt();
imageOutput.seek(imageOutput.getStreamPosition() - 4);
@@ -258,14 +268,15 @@ class DDSImageWriter extends ImageWriterBase {
//dwRGBBitCount
imageOutput.writeInt(type.blockSize() * 8); // TODO: Is bitcount always a multiple of 8?
int[] mask = type.rgbaMasks;
//dwRBitMask
imageOutput.writeInt(type.rgbaMasks[0]);
imageOutput.writeInt(mask[0]);
//dwGBitMask
imageOutput.writeInt(type.rgbaMasks[1]);
imageOutput.writeInt(mask[1]);
//dwBBitMask
imageOutput.writeInt(type.rgbaMasks[2]);
imageOutput.writeInt(mask[2]);
//dwABitMask
imageOutput.writeInt(type.rgbaMasks[3]);
imageOutput.writeInt(mask[3]);
}
else {
//write 5 zero integers as fourCC is used
@@ -291,8 +302,7 @@ class DDSImageWriter extends ImageWriterBase {
imageOutput.writeInt(DDS.PIXEL_FORMAT_FLAG_FOURCC);
}
else {
imageOutput.writeInt(DDS.PIXEL_FORMAT_FLAG_RGB
| (type.rgbaMasks != null && type.rgbaMasks[3] != 0 ? DDS.PIXEL_FORMAT_FLAG_ALPHAPIXELS : 0));
imageOutput.writeInt(DDS.PIXEL_FORMAT_FLAG_RGB | (type.rgbaMasks != null ? DDS.PIXEL_FORMAT_FLAG_ALPHAPIXELS : 0));
}
}
@@ -324,7 +334,6 @@ class DDSImageWriter extends ImageWriterBase {
if (args.length != 1) {
throw new IllegalArgumentException("Use 1 input file at a time.");
}
ImageIO.write(ImageIO.read(new File(args[0])), "dds", new MemoryCacheImageOutputStream(Files.newOutputStream(Paths.get("output.dds"))));
}
}
@@ -33,7 +33,6 @@ package com.twelvemonkeys.imageio.plugins.icns;
import com.twelvemonkeys.imageio.ImageWriterBase;
import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import com.twelvemonkeys.imageio.util.SequenceSupport;
import javax.imageio.IIOException;
import javax.imageio.IIOImage;
@@ -56,7 +55,7 @@ import java.util.Iterator;
*/
public final class ICNSImageWriter extends ImageWriterBase {
private final SequenceSupport sequence = new SequenceSupport();
private int sequenceIndex = -1;
private ImageWriter pngDelegate;
ICNSImageWriter(ImageWriterSpi provider) {
@@ -65,7 +64,7 @@ public final class ICNSImageWriter extends ImageWriterBase {
@Override
protected void resetMembers() {
sequence.reset();
sequenceIndex = -1;
if (pngDelegate != null) {
pngDelegate.dispose();
@@ -98,29 +97,41 @@ public final class ICNSImageWriter extends ImageWriterBase {
@Override
public void prepareWriteSequence(final IIOMetadata streamMetadata) throws IOException {
assertOutput();
sequence.start();
// TODO: Allow TOC resource to be passed as stream metadata?
// - We only need number of icons to be written later
// - The contents of the TOC could be updated while adding to the sequence
if (sequenceIndex >= 0) {
throw new IllegalStateException("writeSequence already started");
}
writeICNSHeader();
sequenceIndex = 0;
}
@SuppressWarnings("RedundantThrows")
@Override
public void endWriteSequence() throws IOException {
assertOutput();
sequence.end();
if (sequenceIndex < 0) {
throw new IllegalStateException("prepareWriteSequence not called");
}
// TODO: Now that we know the number of icon resources, we could move all data backwards
// and write a TOC... But I don't think the benefit will outweigh the cost.
sequenceIndex = -1;
}
@Override
public void writeToSequence(final IIOImage image, final ImageWriteParam param) throws IOException {
assertOutput();
int imageIndex = sequence.advance();
if (sequenceIndex < 0) {
throw new IllegalStateException("prepareWriteSequence not called");
}
if (image.hasRaster()) {
throw new UnsupportedOperationException("image has a Raster");
@@ -137,7 +148,7 @@ public final class ICNSImageWriter extends ImageWriterBase {
imageOutput.writeInt(IconResource.typeFromImage(image.getRenderedImage(), "PNG"));
imageOutput.writeInt(0); // Size, update later
processImageStarted(imageIndex);
processImageStarted(sequenceIndex);
// Write icon in PNG format
ImageWriter writer = getPNGDelegate();
@@ -197,7 +208,7 @@ public final class ICNSImageWriter extends ImageWriterBase {
pngDelegate.addIIOWriteWarningListener(new IIOWriteWarningListener() {
@Override
public void warningOccurred(ImageWriter source, int imageIndex, String warning) {
processWarningOccurred(sequence.current(), warning);
processWarningOccurred(sequenceIndex, warning);
}
});
}
@@ -42,7 +42,6 @@ import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import com.twelvemonkeys.imageio.util.SequenceSupport;
import com.twelvemonkeys.io.enc.EncoderStream;
import com.twelvemonkeys.io.enc.PackBitsEncoder;
import com.twelvemonkeys.lang.Validate;
@@ -111,7 +110,12 @@ public final class TIFFImageWriter extends ImageWriterBase {
// Support storing multiple images in one stream (multi-page TIFF)
// Support more of the ImageIO metadata (ie. compression from metadata, etc)
private final SequenceSupport sequence = new SequenceSupport();
/**
* Flag for active sequence writing
*/
private boolean writingSequence = false;
private int sequenceIndex = 0;
/**
* Metadata writer for sequence writing
@@ -747,7 +751,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
ifd = ((TIFFImageMetadata) inData).getIFD();
}
else {
TIFFImageMetadata outData = new TIFFImageMetadata(Collections.emptySet());
TIFFImageMetadata outData = new TIFFImageMetadata(Collections.<Entry>emptySet());
try {
if (Arrays.asList(inData.getMetadataFormatNames()).contains(SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME)) {
@@ -762,7 +766,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
}
}
catch (IIOInvalidTreeException e) {
processWarningOccurred(sequence.current(), "Could not convert image meta data: " + e.getMessage());
processWarningOccurred(sequenceIndex, "Could not convert image meta data: " + e.getMessage());
}
ifd = outData.getIFD();
@@ -962,11 +966,14 @@ public final class TIFFImageWriter extends ImageWriterBase {
@Override
public void prepareWriteSequence(final IIOMetadata streamMetadata) throws IOException {
sequence.start();
if (writingSequence) {
throw new IllegalStateException("sequence writing has already been started!");
}
assertOutput();
configureStreamByteOrder(streamMetadata, imageOutput);
writingSequence = true;
sequenceTIFFWriter = new TIFFWriter(isBigTIFF() ? 8 : 4);
sequenceTIFFWriter.writeTIFFHeader(imageOutput);
sequenceLastIFDPos = imageOutput.getStreamPosition();
@@ -978,20 +985,26 @@ public final class TIFFImageWriter extends ImageWriterBase {
@Override
public void writeToSequence(final IIOImage image, final ImageWriteParam param) throws IOException {
int sequenceIndex = sequence.advance();
if (!writingSequence) {
throw new IllegalStateException("prepareWriteSequence() must be called before writeToSequence()!");
}
if (sequenceIndex > 0) {
imageOutput.flushBefore(sequenceLastIFDPos);
imageOutput.seek(imageOutput.length());
}
sequenceLastIFDPos = writePage(sequenceIndex, image, param, sequenceTIFFWriter, sequenceLastIFDPos);
sequenceLastIFDPos = writePage(sequenceIndex++, image, param, sequenceTIFFWriter, sequenceLastIFDPos);
}
@Override
public void endWriteSequence() throws IOException {
sequence.end();
if (!writingSequence) {
throw new IllegalStateException("prepareWriteSequence() must be called before endWriteSequence()!");
}
writingSequence = false;
sequenceIndex = 0;
sequenceTIFFWriter = null;
sequenceLastIFDPos = -1;
imageOutput.flush();
@@ -1001,7 +1014,8 @@ public final class TIFFImageWriter extends ImageWriterBase {
protected void resetMembers() {
super.resetMembers();
sequence.reset();
writingSequence = false;
sequenceIndex = 0;
sequenceTIFFWriter = null;
sequenceLastIFDPos = -1;
}
+1 -1
View File
@@ -191,7 +191,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.5.0</version>
<version>3.4.0</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
+1 -1
View File
@@ -82,7 +82,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.2</version>
<version>3.6.1</version>
<executions>
<execution>
<id>jakarta</id>