From 10183ef830e6831b067b0230bed20823be443dac Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Fri, 13 Mar 2026 11:37:47 +0100 Subject: [PATCH] New class for simpler sequence write support. --- .../imageio/util/SequenceSupport.java | 115 ++++++++++++++++++ .../imageio/util/SequenceSupportTest.java | 89 ++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/SequenceSupport.java create mode 100644 imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/SequenceSupportTest.java diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/SequenceSupport.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/SequenceSupport.java new file mode 100644 index 00000000..fe46a87d --- /dev/null +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/SequenceSupport.java @@ -0,0 +1,115 @@ +/* + * 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; + } +} diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/SequenceSupportTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/SequenceSupportTest.java new file mode 100644 index 00000000..a2432e5d --- /dev/null +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/SequenceSupportTest.java @@ -0,0 +1,89 @@ +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); + } +} \ No newline at end of file