diff --git a/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathBuilder.java b/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathBuilder.java index 44213335..0a43af92 100755 --- a/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathBuilder.java +++ b/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathBuilder.java @@ -28,9 +28,9 @@ public final class AdobePathBuilder { } /** - * @see AdobePathReader#path() + * @see AdobePathReader#readPath() */ public Path2D path() throws IOException { - return delegate.path(); + return delegate.readPath(); } } diff --git a/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathReader.java b/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathReader.java index 93472acd..cae9d8dd 100755 --- a/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathReader.java +++ b/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Harald Kuhr + * Copyright (c) 2014-2020, Harald Kuhr * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,7 +33,6 @@ package com.twelvemonkeys.imageio.path; import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; import javax.imageio.IIOException; -import java.awt.geom.GeneralPath; import java.awt.geom.Path2D; import java.io.DataInput; import java.io.EOFException; @@ -90,7 +89,7 @@ public final class AdobePathReader { * @throws javax.imageio.IIOException if the input contains a bad path data. * @throws IOException if a general I/O exception occurs during reading. */ - public Path2D path() throws IOException { + public Path2D readPath() throws IOException { List> subPaths = new ArrayList<>(); List currentPath = null; int currentPathLength = 0; @@ -110,8 +109,8 @@ public final class AdobePathReader { subPaths.add(currentPath); } - currentPath = new ArrayList<>(segment.length); - currentPathLength = segment.length; + currentPath = new ArrayList<>(segment.lengthOrRule); + currentPathLength = segment.lengthOrRule; } else if (segment.selector == AdobePathSegment.OPEN_SUBPATH_BEZIER_LINKED || segment.selector == AdobePathSegment.OPEN_SUBPATH_BEZIER_UNLINKED @@ -149,8 +148,8 @@ public final class AdobePathReader { * closePath() */ private Path2D pathToShape(final List> paths) { - GeneralPath path = new GeneralPath(Path2D.WIND_EVEN_ODD, paths.size()); - GeneralPath subpath = null; + Path2D path = new Path2D.Float(Path2D.WIND_EVEN_ODD, paths.size()); + Path2D subpath = null; for (List points : paths) { int length = points.size(); @@ -163,7 +162,7 @@ public final class AdobePathReader { switch (step) { // Begin case 0: { - subpath = new GeneralPath(Path2D.WIND_EVEN_ODD, length); + subpath = new Path2D.Float(Path2D.WIND_EVEN_ODD, length); subpath.moveTo(current.apx, current.apy); if (length > 1) { @@ -222,14 +221,12 @@ public final class AdobePathReader { switch (selector) { case AdobePathSegment.INITIAL_FILL_RULE_RECORD: case AdobePathSegment.PATH_FILL_RULE_RECORD: - // Spec says Fill rule is ignored by Photoshop - data.skipBytes(24); - return new AdobePathSegment(selector); + // Spec says Fill rule is ignored by Photoshop, we'll read it anyway case AdobePathSegment.CLOSED_SUBPATH_LENGTH_RECORD: case AdobePathSegment.OPEN_SUBPATH_LENGTH_RECORD: - int size = data.readUnsignedShort(); + int lengthOrRule = data.readUnsignedShort(); data.skipBytes(22); - return new AdobePathSegment(selector, size); + return new AdobePathSegment(selector, lengthOrRule); default: return new AdobePathSegment( selector, diff --git a/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathSegment.java b/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathSegment.java index dae391a9..d5cc82cf 100755 --- a/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathSegment.java +++ b/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathSegment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Harald Kuhr + * Copyright (c) 2014-2020, Harald Kuhr * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -63,7 +63,7 @@ final class AdobePathSegment { }; final int selector; - final int length; + final int lengthOrRule; // TODO: Consider keeping these in 8.24FP format // Control point preceding knot @@ -85,18 +85,14 @@ final class AdobePathSegment { this(selector, -1, cppy, cppx, apy, apx, cply, cplx); } - AdobePathSegment(int fillRuleSelector) { - this(isTrue(fillRuleSelector == PATH_FILL_RULE_RECORD || fillRuleSelector == INITIAL_FILL_RULE_RECORD, fillRuleSelector, "Expected fill rule record (6 or 8): %s"), - 0, -1, -1, -1, -1, -1, -1); - } - - AdobePathSegment(final int lengthSelector, final int length) { - this(isTrue(lengthSelector == CLOSED_SUBPATH_LENGTH_RECORD || lengthSelector == OPEN_SUBPATH_LENGTH_RECORD, lengthSelector, "Expected path length record (0 or 3): %s"), - length, + AdobePathSegment(final int selector, final int lengthOrRule) { + this(isTrue(selector == CLOSED_SUBPATH_LENGTH_RECORD || selector == OPEN_SUBPATH_LENGTH_RECORD + || selector == PATH_FILL_RULE_RECORD || selector == INITIAL_FILL_RULE_RECORD, selector, "Expected path length or fill rule record (0/3 or 6/8): %s"), + lengthOrRule, -1, -1, -1, -1, -1, -1); } - private AdobePathSegment(final int selector, final int length, + private AdobePathSegment(final int selector, final int lengthOrRule, final double cppy, final double cppx, final double apy, final double apx, final double cply, final double cplx) { @@ -104,7 +100,7 @@ final class AdobePathSegment { switch (selector) { case CLOSED_SUBPATH_LENGTH_RECORD: case OPEN_SUBPATH_LENGTH_RECORD: - isTrue(length >= 0, length, "Expected positive length: %d"); + isTrue(lengthOrRule >= 0, lengthOrRule, "Expected positive length: %d"); break; case CLOSED_SUBPATH_BEZIER_LINKED: case CLOSED_SUBPATH_BEZIER_UNLINKED: @@ -116,15 +112,17 @@ final class AdobePathSegment { ); break; case PATH_FILL_RULE_RECORD: - case CLIPBOARD_RECORD: case INITIAL_FILL_RULE_RECORD: + isTrue(lengthOrRule == 0 || lengthOrRule == 1, lengthOrRule, "Expected rule (1 or 0): %d"); + break; + case CLIPBOARD_RECORD: break; default: throw new IllegalArgumentException("Unknown selector: " + selector); } this.selector = selector; - this.length = length; + this.lengthOrRule = lengthOrRule; this.cppy = cppy; this.cppx = cppx; this.apy = apy; @@ -152,7 +150,7 @@ final class AdobePathSegment { && Double.compare(that.cppx, cppx) == 0 && Double.compare(that.cppy, cppy) == 0 && selector == that.selector - && length == that.length; + && lengthOrRule == that.lengthOrRule; } @@ -161,7 +159,7 @@ final class AdobePathSegment { long tempBits; int result = selector; - result = 31 * result + length; + result = 31 * result + lengthOrRule; tempBits = Double.doubleToLongBits(cppy); result = 31 * result + (int) (tempBits ^ (tempBits >>> 32)); tempBits = Double.doubleToLongBits(cppx); @@ -183,10 +181,10 @@ final class AdobePathSegment { switch (selector) { case INITIAL_FILL_RULE_RECORD: case PATH_FILL_RULE_RECORD: - return String.format("Rule(selector=%s, rule=%d)", SELECTOR_NAMES[selector], length); + return String.format("Rule(selector=%s, rule=%d)", SELECTOR_NAMES[selector], lengthOrRule); case CLOSED_SUBPATH_LENGTH_RECORD: case OPEN_SUBPATH_LENGTH_RECORD: - return String.format("Len(selector=%s, length=%d)", SELECTOR_NAMES[selector], length); + return String.format("Len(selector=%s, length=%d)", SELECTOR_NAMES[selector], lengthOrRule); default: // fall-through } diff --git a/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathWriter.java b/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathWriter.java index 7ab95a95..735ffee7 100755 --- a/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathWriter.java +++ b/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathWriter.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2020 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.path; import com.twelvemonkeys.imageio.metadata.psd.PSD; @@ -13,6 +43,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import static com.twelvemonkeys.imageio.path.AdobePathReader.DEBUG; import static com.twelvemonkeys.imageio.path.AdobePathSegment.*; import static com.twelvemonkeys.lang.Validate.isTrue; import static com.twelvemonkeys.lang.Validate.notNull; @@ -27,10 +58,10 @@ public final class AdobePathWriter { /** * Creates an AdobePathWriter for the given path. *

- * NOTE: Photoshop paths are stored with the coordinates - * (0,0) representing the top left corner of the image, - * and (1,1) representing the bottom right corner, - * regardless of image dimensions. + * NOTE: Photoshop paths are stored with the coordinates + * (0,0) representing the top left corner of the image, + * and (1,1) representing the bottom right corner, + * regardless of image dimensions. *

* * @param path A {@code Path2D} instance that has {@link Path2D#WIND_EVEN_ODD WIND_EVEN_ODD} rule @@ -51,16 +82,24 @@ public final class AdobePathWriter { // TODO: Look at the API so that conversion both ways are aligned. The read part builds a path from List... private static List pathToSegments(final PathIterator pathIterator) { double[] coords = new double[6]; - AdobePathSegment prev = new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, 0, 0, 0,0, 0, 0); + AdobePathSegment prev = new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, 0, 0, 0, 0, 0, 0); List subpath = new ArrayList<>(); List segments = new ArrayList<>(); - segments.add(new AdobePathSegment(PATH_FILL_RULE_RECORD)); + segments.add(new AdobePathSegment(PATH_FILL_RULE_RECORD, 0)); + segments.add(new AdobePathSegment(INITIAL_FILL_RULE_RECORD, 0)); while (!pathIterator.isDone()) { int segmentType = pathIterator.currentSegment(coords); - System.out.println("segmentType: " + segmentType); - System.err.println("coords: " + Arrays.toString(coords)); + + if (DEBUG) { + System.out.println("segmentType: " + segmentType); + System.err.println("coords: " + Arrays.toString(coords)); + } + + // TODO: We need to support unlinked segments! + + boolean collinear; switch (segmentType) { case PathIterator.SEG_MOVETO: @@ -71,17 +110,23 @@ public final class AdobePathWriter { break; case PathIterator.SEG_LINETO: - subpath.add(new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, prev.cppy, prev.cppx, prev.apy, prev.apx, coords[1], coords[0])); + collinear = isCollinearAndSameDistance(prev.cppx, prev.cppy, prev.apx, prev.apy, coords[0], coords[1]); + System.out.println("isCollinear? " + collinear); + subpath.add(new AdobePathSegment(collinear ? CLOSED_SUBPATH_BEZIER_LINKED : CLOSED_SUBPATH_BEZIER_UNLINKED, prev.cppy, prev.cppx, prev.apy, prev.apx, coords[1], coords[0])); prev = new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, coords[1], coords[0], coords[1], coords[0], 0, 0); break; case PathIterator.SEG_QUADTO: - subpath.add(new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, prev.cppy, prev.cppx, prev.apy, prev.apx, coords[1], coords[0])); + collinear = isCollinearAndSameDistance(prev.cppx, prev.cppy, prev.apx, prev.apy, coords[0], coords[1]); + System.out.println("isCollinear? " + collinear); + subpath.add(new AdobePathSegment(collinear ? CLOSED_SUBPATH_BEZIER_LINKED : CLOSED_SUBPATH_BEZIER_UNLINKED, prev.cppy, prev.cppx, prev.apy, prev.apx, coords[1], coords[0])); prev = new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, coords[3], coords[2], coords[3], coords[2], 0, 0); break; case PathIterator.SEG_CUBICTO: - subpath.add(new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, prev.cppy, prev.cppx, prev.apy, prev.apx, coords[1], coords[0])); + collinear = isCollinearAndSameDistance(prev.cppx, prev.cppy, prev.apx, prev.apy, coords[0], coords[1]); + System.out.println("isCollinear? " + collinear); + subpath.add(new AdobePathSegment(collinear ? CLOSED_SUBPATH_BEZIER_LINKED : CLOSED_SUBPATH_BEZIER_UNLINKED, prev.cppy, prev.cppx, prev.apy, prev.apx, coords[1], coords[0])); prev = new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, coords[3], coords[2], coords[5], coords[4], 0, 0); break; @@ -93,7 +138,10 @@ public final class AdobePathWriter { // subpath.add(new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, prev.cppy, prev.cppx, prev.apy, prev.apx, 0, 0)); throw new AssertionError("Not a closed path"); } - subpath.set(0, new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, prev.cppy, prev.cppx, initial.apy, initial.apx, initial.cply, initial.cplx)); + + collinear = isCollinearAndSameDistance(prev.cppx, prev.cppy, initial.apx, initial.apy, initial.cplx, initial.cply); + System.out.println("isCollinear? " + collinear); + subpath.set(0, new AdobePathSegment(collinear ? CLOSED_SUBPATH_BEZIER_LINKED : CLOSED_SUBPATH_BEZIER_UNLINKED, prev.cppy, prev.cppx, initial.apy, initial.apx, initial.cply, initial.cplx)); // Add to full path segments.add(new AdobePathSegment(CLOSED_SUBPATH_LENGTH_RECORD, subpath.size())); @@ -110,17 +158,38 @@ public final class AdobePathWriter { return segments; } - public void writePath(final DataOutput output) throws IOException { - System.err.println("segments: " + segments.size()); + private static final double COLLINEARITY_THRESHOLD = 0.035; + private static boolean isCollinearAndSameDistance(double x1, double y1, double x2, double y2, double x3, double y3) { +// return (y3 - y2) * (x2 - x1) == (y2 - y1) * (x3 - x2); // Collinear Slope +// return x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2) == 0; // Collinear (Double) Area + +// return Math.abs(x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) <= 0.0005; // With some slack... + +// return Math.abs(Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)) - Math.sqrt(Math.pow(x3 - x2, 2) + Math.pow(y3 - y2, 2))) <= 0.01; + + // TODO: Get hold of a real Photoshop sample... The current data may be wrong. + // TODO: If correct, PS writes linked if all points are the same... + return (x1 == x2 && x2 == x3 && y1 == y2 && y2 == y3) || + (x1 != x2 || y1 != y2) && (x2 != x3 || y2 != y3) && Math.abs((x2 - x1) - (x3 - x2)) <= COLLINEARITY_THRESHOLD && Math.abs((y2 - y1) - (y3 - y2)) <= COLLINEARITY_THRESHOLD; + } + + void writePathResource(final DataOutput output) throws IOException { output.writeInt(PSD.RESOURCE_TYPE); output.writeShort(PSD.RES_CLIPPING_PATH); output.writeShort(0); // Path name (Pascal string) empty + pad output.writeInt(segments.size() * 26); // Resource size + writePath(output); + } + + public void writePath(final DataOutput output) throws IOException { + if (DEBUG) { + System.err.println("segments: " + segments.size()); + System.err.println(segments); + } for (AdobePathSegment segment : segments) { - System.err.println(segment); switch (segment.selector) { case PATH_FILL_RULE_RECORD: case INITIAL_FILL_RULE_RECORD: @@ -132,7 +201,7 @@ public final class AdobePathWriter { case OPEN_SUBPATH_LENGTH_RECORD: case CLOSED_SUBPATH_LENGTH_RECORD: output.writeShort(segment.selector); - output.writeShort(segment.length); // Subpath length + output.writeShort(segment.lengthOrRule); // Subpath length output.write(new byte[22]); break; default: @@ -148,13 +217,15 @@ public final class AdobePathWriter { } } - public byte[] createPath() { + // TODO: Better name? + public byte[] writePath() { // TODO: Do we need to care about endianness for TIFF files? ByteArrayOutputStream bytes = new ByteArrayOutputStream(); try (DataOutputStream stream = new DataOutputStream(bytes)) { writePath(stream); - } catch (IOException e) { + } + catch (IOException e) { throw new AssertionError("Should never.. uh.. Oh well. It happened.", e); } diff --git a/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/Paths.java b/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/Paths.java index e4d62c62..7cf3e33f 100755 --- a/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/Paths.java +++ b/imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/Paths.java @@ -163,10 +163,10 @@ public final class Paths { System.out.println("resourceBlocks: " + resourceBlocks); } - Entry resourceBlock = resourceBlocks.getEntryById(PSD.RES_CLIPPING_PATH); + Entry pathResource = resourceBlocks.getEntryById(PSD.RES_CLIPPING_PATH); - if (resourceBlock != null) { - return new AdobePathReader((byte[]) resourceBlock.getValue()).path(); + if (pathResource != null) { + return new AdobePathReader((byte[]) pathResource.getValue()).readPath(); } return null; @@ -259,9 +259,7 @@ public final class Paths { BufferedImage destination; if (args.length == 1) { // Embedded path - try (ImageInputStream input = ImageIO.createImageInputStream(new File(args[0]))) { - destination = readClipped(input); - } + destination = readClipped(ImageIO.createImageInputStream(new File(args[0]))); } else { // Separate path and image @@ -282,5 +280,4 @@ public final class Paths { System.err.printf("%s not deleted\n", tempFile); } } - } diff --git a/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathBuilderTest.java b/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathBuilderTest.java index 1e917a86..ddd05dc5 100644 --- a/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathBuilderTest.java +++ b/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathBuilderTest.java @@ -43,6 +43,7 @@ import static com.twelvemonkeys.imageio.path.PathsTest.assertPathEquals; import static com.twelvemonkeys.imageio.path.PathsTest.readExpectedPath; import static org.junit.Assert.assertNotNull; +@SuppressWarnings("deprecation") public class AdobePathBuilderTest { @Test(expected = IllegalArgumentException.class) diff --git a/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathReaderTest.java b/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathReaderTest.java new file mode 100644 index 00000000..2837bbe9 --- /dev/null +++ b/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathReaderTest.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2020 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.path; + +import org.junit.Test; + +import javax.imageio.IIOException; +import javax.imageio.stream.ImageInputStream; +import java.awt.geom.Path2D; +import java.io.DataInput; +import java.io.IOException; +import java.nio.ByteBuffer; + +import static com.twelvemonkeys.imageio.path.PathsTest.assertPathEquals; +import static com.twelvemonkeys.imageio.path.PathsTest.readExpectedPath; +import static org.junit.Assert.assertNotNull; + +public class AdobePathReaderTest { + + @Test(expected = IllegalArgumentException.class) + public void testCreateNullBytes() { + new AdobePathReader((byte[]) null); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateNull() { + new AdobePathReader((DataInput) null); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateEmpty() { + new AdobePathReader(new byte[0]); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateShortPath() { + new AdobePathReader(new byte[3]); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateImpossiblePath() { + new AdobePathReader(new byte[7]); + } + + @Test + public void testCreate() { + new AdobePathReader(new byte[52]); + } + + @Test + public void testNoPath() throws IOException { + Path2D path = new AdobePathReader(new byte[26]).readPath(); + assertNotNull(path); + } + + @Test(expected = IIOException.class) + public void testShortPath() throws IOException { + byte[] data = new byte[26]; + ByteBuffer buffer = ByteBuffer.wrap(data); + buffer.putShort((short) AdobePathSegment.CLOSED_SUBPATH_LENGTH_RECORD); + buffer.putShort((short) 1); + + Path2D path = new AdobePathReader(data).readPath(); + assertNotNull(path); + } + + @Test(expected = IIOException.class) + public void testShortPathToo() throws IOException { + byte[] data = new byte[52]; + ByteBuffer buffer = ByteBuffer.wrap(data); + buffer.putShort((short) AdobePathSegment.CLOSED_SUBPATH_LENGTH_RECORD); + buffer.putShort((short) 2); + buffer.position(buffer.position() + 22); + buffer.putShort((short) AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED); + + Path2D path = new AdobePathReader(data).readPath(); + assertNotNull(path); + } + + @Test(expected = IIOException.class) + public void testLongPath() throws IOException { + byte[] data = new byte[78]; + ByteBuffer buffer = ByteBuffer.wrap(data); + buffer.putShort((short) AdobePathSegment.CLOSED_SUBPATH_LENGTH_RECORD); + buffer.putShort((short) 1); + buffer.position(buffer.position() + 22); + buffer.putShort((short) AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED); + buffer.position(buffer.position() + 24); + buffer.putShort((short) AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED); + + Path2D path = new AdobePathReader(data).readPath(); + assertNotNull(path); + } + + @Test(expected = IIOException.class) + public void testPathMissingLength() throws IOException { + byte[] data = new byte[26]; + ByteBuffer buffer = ByteBuffer.wrap(data); + buffer.putShort((short) AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED); + + Path2D path = new AdobePathReader(data).readPath(); + assertNotNull(path); + } + + @Test + public void testSimplePath() throws IOException { + // We'll read this from a real file, with hardcoded offsets for simplicity + // PSD IRB: offset: 34, length: 32598 + // Clipping path: offset: 31146, length: 1248 + ImageInputStream stream = PathsTest.resourceAsIIOStream("/psd/grape_with_path.psd"); + stream.seek(34 + 31146); + byte[] data = new byte[1248]; + stream.readFully(data); + + Path2D path = new AdobePathReader(data).readPath(); + + assertNotNull(path); + assertPathEquals(path, readExpectedPath("/ser/grape-path.ser")); + } + + @Test + public void testComplexPath() throws IOException { + // We'll read this from a real file, with hardcoded offsets for simplicity + // PSD IRB: offset: 16970, length: 11152 + // Clipping path: offset: 9250, length: 1534 + ImageInputStream stream = PathsTest.resourceAsIIOStream("/tiff/big-endian-multiple-clips.tif"); + stream.seek(16970 + 9250); + byte[] data = new byte[1534]; + stream.readFully(data); + + Path2D path = new AdobePathReader(data).readPath(); + + assertNotNull(path); + assertPathEquals(path, readExpectedPath("/ser/multiple-clips.ser")); + } +} \ No newline at end of file diff --git a/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathSegmentTest.java b/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathSegmentTest.java index df7d98c1..7b4827e4 100644 --- a/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathSegmentTest.java +++ b/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathSegmentTest.java @@ -63,7 +63,7 @@ public class AdobePathSegmentTest { AdobePathSegment segment = new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_LENGTH_RECORD, 42); assertEquals(AdobePathSegment.OPEN_SUBPATH_LENGTH_RECORD, segment.selector); - assertEquals(42, segment.length); + assertEquals(42, segment.lengthOrRule); assertEquals(-1, segment.cppx, 0); assertEquals(-1, segment.cppy, 0); assertEquals(-1, segment.apx, 0); @@ -82,7 +82,7 @@ public class AdobePathSegmentTest { AdobePathSegment segment = new AdobePathSegment(AdobePathSegment.CLOSED_SUBPATH_LENGTH_RECORD, 27); assertEquals(AdobePathSegment.CLOSED_SUBPATH_LENGTH_RECORD, segment.selector); - assertEquals(27, segment.length); + assertEquals(27, segment.lengthOrRule); assertEquals(-1, segment.cppx, 0); assertEquals(-1, segment.cppy, 0); assertEquals(-1, segment.apx, 0); @@ -98,7 +98,7 @@ public class AdobePathSegmentTest { AdobePathSegment segment = new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_BEZIER_LINKED, .5, .5, 0, 0, 1, 1); assertEquals(AdobePathSegment.OPEN_SUBPATH_BEZIER_LINKED, segment.selector); - assertEquals(-1, segment.length); + assertEquals(-1, segment.lengthOrRule); assertEquals(.5, segment.cppx, 0); assertEquals(.5, segment.cppy, 0); assertEquals(0, segment.apx, 0); @@ -122,7 +122,7 @@ public class AdobePathSegmentTest { AdobePathSegment segment = new AdobePathSegment(AdobePathSegment.OPEN_SUBPATH_BEZIER_UNLINKED, .5, .5, 0, 0, 1, 1); assertEquals(AdobePathSegment.OPEN_SUBPATH_BEZIER_UNLINKED, segment.selector); - assertEquals(-1, segment.length); + assertEquals(-1, segment.lengthOrRule); assertEquals(.5, segment.cppx, 0); assertEquals(.5, segment.cppy, 0); assertEquals(0, segment.apx, 0); @@ -149,7 +149,7 @@ public class AdobePathSegmentTest { AdobePathSegment segment = new AdobePathSegment(AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED, .5, .5, 0, 0, 1, 1); assertEquals(AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED, segment.selector); - assertEquals(-1, segment.length); + assertEquals(-1, segment.lengthOrRule); assertEquals(.5, segment.cppx, 0); assertEquals(.5, segment.cppy, 0); assertEquals(0, segment.apx, 0); @@ -173,7 +173,7 @@ public class AdobePathSegmentTest { AdobePathSegment segment = new AdobePathSegment(AdobePathSegment.CLOSED_SUBPATH_BEZIER_UNLINKED, .5, .5, 0, 0, 1, 1); assertEquals(AdobePathSegment.CLOSED_SUBPATH_BEZIER_UNLINKED, segment.selector); - assertEquals(-1, segment.length); + assertEquals(-1, segment.lengthOrRule); assertEquals(.5, segment.cppx, 0); assertEquals(.5, segment.cppy, 0); assertEquals(0, segment.apx, 0); @@ -195,7 +195,7 @@ public class AdobePathSegmentTest { @Test public void testToStringRule() { - String string = new AdobePathSegment(AdobePathSegment.INITIAL_FILL_RULE_RECORD).toString(); + String string = new AdobePathSegment(AdobePathSegment.INITIAL_FILL_RULE_RECORD, 0).toString(); assertTrue(string, string.startsWith("Rule")); assertTrue(string, string.contains("Initial")); assertTrue(string, string.contains("fill")); diff --git a/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathWriterTest.java b/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathWriterTest.java new file mode 100644 index 00000000..f46d02c2 --- /dev/null +++ b/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/AdobePathWriterTest.java @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2020 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.path; + +import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; +import org.junit.Test; + +import javax.imageio.ImageIO; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.ImageOutputStream; +import java.awt.*; +import java.awt.geom.*; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; + +import static com.twelvemonkeys.imageio.path.AdobePathSegment.*; +import static com.twelvemonkeys.imageio.path.PathsTest.assertPathEquals; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +/** + * AdobePathWriterTest. + * + * @author Harald Kuhr + * @author last modified by haraldk: harald.kuhr$ + * @version : AdobePathWriterTest.java,v 1.0 2020-01-02 harald.kuhr Exp$ + */ +public class AdobePathWriterTest { + + @Test(expected = IllegalArgumentException.class) + public void testCreateWriterNull() { + new AdobePathWriter(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateWriterInvalid() { + new AdobePathWriter(new Path2D.Double(Path2D.WIND_NON_ZERO)); + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateWriterOutOfBounds() { + Path2D path = new Path2D.Float(Path2D.WIND_EVEN_ODD); + path.append(new Ellipse2D.Double(.5, 0.5, 2, 2), false); + + new AdobePathWriter(path); + } + + @Test + public void testCreateWriterValid() { + Path2D path = new Path2D.Float(Path2D.WIND_EVEN_ODD); + path.append(new Ellipse2D.Double(.25, .25, .5, .5), false); + + new AdobePathWriter(path); + } + + @Test + public void testCreateWriterMulti() { + Path2D path = new GeneralPath(Path2D.WIND_EVEN_ODD); + path.append(new Ellipse2D.Float(.25f, .25f, .5f, .5f), false); + path.append(new Rectangle2D.Double(0, 0, 1, .5), false); + path.append(new Polygon(new int[] {1, 2, 0, 1}, new int[] {0, 2, 2, 0}, 4) + .getPathIterator(AffineTransform.getScaleInstance(1 / 2.0, 1 / 2.0)), false); + + new AdobePathWriter(path); + } + + @Test + public void testWriteToStream() throws IOException { + Path2D path = new GeneralPath(Path2D.WIND_EVEN_ODD); + path.append(new Ellipse2D.Double(0, 0, 1, 1), false); + path.append(new Ellipse2D.Double(.5, .5, .5, .5), false); + path.append(new Ellipse2D.Double(.25, .25, .5, .5), false); + + AdobePathWriter pathCreator = new AdobePathWriter(path); + + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + try (ImageOutputStream output = ImageIO.createImageOutputStream(byteStream)) { + pathCreator.writePath(output); + } + + assertEquals(17 * 26, byteStream.size()); + + byte[] bytes = byteStream.toByteArray(); + + int off = 0; + + // Path/initial fill rule: Even-Odd (0) + assertArrayEquals(new byte[] {0, PATH_FILL_RULE_RECORD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, INITIAL_FILL_RULE_RECORD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Elipse 1: 0, 0, 1, 1 + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_LENGTH_RECORD, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 0, 57, 78, -68, 1, 0, 0, 0, 0, -128, 0, 0, 1, 0, 0, 0, 0, -58, -79, 68, 1, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 1, 0, 0, 0, 0, -58, -79, 68, 1, 0, 0, 0, 0, -128, 0, 0, 1, 0, 0, 0, 0, 57, 78, -68}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 0, -58, -79, 68, 0, 0, 0, 0, 0, -128, 0, 0, 0, 0, 0, 0, 0, 57, 78, -68, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 0, 0, 0, 0, 0, 57, 78, -68, 0, 0, 0, 0, 0, -128, 0, 0, 0, 0, 0, 0, 0, -58, -79, 68}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Elipse 2: .5, .5, .5, .5 + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_LENGTH_RECORD, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 0, -100, -89, 94, 1, 0, 0, 0, 0, -64, 0, 0, 1, 0, 0, 0, 0, -29, 88, -94, 1, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 1, 0, 0, 0, 0, -29, 88, -94, 1, 0, 0, 0, 0, -64, 0, 0, 1, 0, 0, 0, 0, -100, -89, 94}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 0, -29, 88, -94, 0, -128, 0, 0, 0, -64, 0, 0, 0, -128, 0, 0, 0, -100, -89, 94, 0, -128, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 0, -128, 0, 0, 0, -100, -89, 94, 0, -128, 0, 0, 0, -64, 0, 0, 0, -128, 0, 0, 0, -29, 88, -94}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Elipse32: .25, .25, .5, .5 + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_LENGTH_RECORD, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 0, 92, -89, 94, 0, -64, 0, 0, 0, -128, 0, 0, 0, -64, 0, 0, 0, -93, 88, -94, 0, -64, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 0, -64, 0, 0, 0, -93, 88, -94, 0, -64, 0, 0, 0, -128, 0, 0, 0, -64, 0, 0, 0, 92, -89, 94}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 0, -93, 88, -94, 0, 64, 0, 0, 0, -128, 0, 0, 0, 64, 0, 0, 0, 92, -89, 94, 0, 64, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_LINKED, 0, 64, 0, 0, 0, 92, -89, 94, 0, 64, 0, 0, 0, -128, 0, 0, 0, 64, 0, 0, 0, -93, 88, -94}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Sanity + assertEquals(bytes.length, off); + } + + @Test + public void testCreateArray() { + Path2D path = new GeneralPath(Path2D.WIND_EVEN_ODD); + path.append(new Rectangle2D.Double(0, 0, 1, .5), false); + path.append(new Rectangle2D.Double(.25, .25, .5, .5), false); + + AdobePathWriter pathCreator = new AdobePathWriter(path); + + byte[] bytes = pathCreator.writePath(); + System.err.println(Arrays.toString(bytes)); + + assertEquals(12 * 26, bytes.length); + + int off = 0; + + // Path/initial fill rule: Even-Odd (0) + assertArrayEquals(new byte[] {0, PATH_FILL_RULE_RECORD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, INITIAL_FILL_RULE_RECORD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Rectangle 1: 0, 0, 1, .5 + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_LENGTH_RECORD, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, -128, 0, 0, 1, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, -128, 0, 0, 1, 0, 0, 0, 0, -128, 0, 0, 1, 0, 0, 0, 0, -128, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, -128, 0, 0, 0, 0, 0, 0, 0, -128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Rectangle 2: .25, .25, .5, .5 + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_LENGTH_RECORD, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, 64, 0, 0, 0, 64, 0, 0, 0, 64, 0, 0, 0, 64, 0, 0, 0, 64, 0, 0, 0, -64, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, 64, 0, 0, 0, -64, 0, 0, 0, 64, 0, 0, 0, -64, 0, 0, 0, -64, 0, 0, 0, -64, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, -64, 0, 0, 0, -64, 0, 0, 0, -64, 0, 0, 0, -64, 0, 0, 0, -64, 0, 0, 0, 64, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 0, -64, 0, 0, 0, 64, 0, 0, 0, -64, 0, 0, 0, 64, 0, 0, 0, 64, 0, 0, 0, 64, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Sanity + assertEquals(bytes.length, off); + } + + @Test + public void testRoundtrip0() throws IOException { + Path2D path = new GeneralPath(Path2D.WIND_EVEN_ODD); + path.append(new Rectangle2D.Double(0, 0, 1, .5), false); + path.append(new Rectangle2D.Double(.25, .25, .5, .5), false); + + byte[] bytes = new AdobePathWriter(path).writePath(); + Path2D readPath = new AdobePathReader(new ByteArrayImageInputStream(bytes)).readPath(); + + assertEquals(path.getWindingRule(), readPath.getWindingRule()); + assertEquals(path.getBounds2D(), readPath.getBounds2D()); + + // TODO: Would be nice, but hard to do, as we convert all points to cubic... +// assertPathEquals(path, readPath); + } + + @Test + public void testRoundtrip1() throws IOException { + // We'll read this from a real file, with hardcoded offsets for simplicity + // PSD IRB: offset: 34, length: 32598 + // Clipping path: offset: 31146, length: 1248 + ImageInputStream stream = PathsTest.resourceAsIIOStream("/psd/grape_with_path.psd"); + stream.seek(34 + 31146); + byte[] data = new byte[1248]; + stream.readFully(data); + + Path2D path = new AdobePathReader(data).readPath(); + byte[] bytes = new AdobePathWriter(path).writePath(); + + Path2D readPath = new AdobePathReader(new ByteArrayImageInputStream(bytes)).readPath(); + assertEquals(path.getWindingRule(), readPath.getWindingRule()); + assertEquals(path.getBounds2D(), readPath.getBounds2D()); + + assertPathEquals(path, readPath); + + assertEquals(data.length, bytes.length); + + // TODO: We currently write all points as linked, this is probably wrong + // Also... Photoshop does write "something" undocumented in the filler bytes for the length records, which may or may not be important... +// assertEquals(formatSegments(data), formatSegments(bytes)); +// assertArrayEquals(data, bytes); + } + + static String formatSegments(byte[] data) { + StringBuilder builder = new StringBuilder(data.length * 5); + + for (int i = 0; i < data.length; i += 26) { + builder.append(Arrays.toString(Arrays.copyOfRange(data, i, i + 26))).append('\n'); + } + + return builder.toString(); + } + + @Test + public void testRoundtrip2() throws IOException { + // We'll read this from a real file, with hardcoded offsets for simplicity + // PSD IRB: offset: 16970, length: 11152 + // Clipping path: offset: 9250, length: 1534 + ImageInputStream stream = PathsTest.resourceAsIIOStream("/tiff/big-endian-multiple-clips.tif"); + stream.seek(16970 + 9250); + byte[] data = new byte[1534]; + stream.readFully(data); + + Path2D path = new AdobePathReader(data).readPath(); + byte[] bytes = new AdobePathWriter(path).writePath(); + + Path2D readPath = new AdobePathReader(new ByteArrayImageInputStream(bytes)).readPath(); + assertEquals(path.getWindingRule(), readPath.getWindingRule()); + assertEquals(path.getBounds2D(), readPath.getBounds2D()); + + assertPathEquals(path, readPath); + + assertEquals(data.length, bytes.length); + + // TODO: We currently write all points as linked, this is probably wrong + // Also... Photoshop does write "something" undocumented in the filler bytes for the length records, which may or may not be important... +// assertEquals(formatSegments(data), formatSegments(bytes)); +// assertArrayEquals(data, bytes); + } +} diff --git a/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/PathsTest.java b/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/PathsTest.java index 7aca08ee..e40e034b 100644 --- a/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/PathsTest.java +++ b/imageio/imageio-clippath/src/test/java/com/twelvemonkeys/imageio/path/PathsTest.java @@ -125,12 +125,12 @@ public class PathsTest { } @Test(expected = IllegalArgumentException.class) - public void testApplyClippingPathNullPath() throws IOException { + public void testApplyClippingPathNullPath() { Paths.applyClippingPath(null, new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY)); } @Test(expected = IllegalArgumentException.class) - public void testApplyClippingPathNullSource() throws IOException { + public void testApplyClippingPathNullSource() { Paths.applyClippingPath(new GeneralPath(), null); } @@ -147,7 +147,7 @@ public class PathsTest { assertEquals(source.getWidth(), image.getWidth()); assertEquals(source.getHeight(), image.getHeight()); // Transparent - assertTrue(image.getColorModel().getTransparency() == Transparency.TRANSLUCENT); + assertEquals(Transparency.TRANSLUCENT, image.getColorModel().getTransparency()); // Corners (at least) should be transparent assertEquals(0, image.getRGB(0, 0)); @@ -161,8 +161,9 @@ public class PathsTest { // TODO: Mor sophisticated test that tests all pixels outside path... } + @SuppressWarnings("ConstantConditions") @Test(expected = IllegalArgumentException.class) - public void testApplyClippingPathNullDestination() throws IOException { + public void testApplyClippingPathNullDestination() { Paths.applyClippingPath(new GeneralPath(), new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY), null); } @@ -209,7 +210,7 @@ public class PathsTest { assertEquals(857, image.getWidth()); assertEquals(1800, image.getHeight()); // Transparent - assertTrue(image.getColorModel().getTransparency() == Transparency.TRANSLUCENT); + assertEquals(Transparency.TRANSLUCENT, image.getColorModel().getTransparency()); // Corners (at least) should be transparent assertEquals(0, image.getRGB(0, 0)); @@ -230,34 +231,34 @@ public class PathsTest { } static Path2D readExpectedPath(final String resource) throws IOException { - ObjectInputStream ois = new ObjectInputStream(PathsTest.class.getResourceAsStream(resource)); - - try { + try (ObjectInputStream ois = new ObjectInputStream(PathsTest.class.getResourceAsStream(resource))) { return (Path2D) ois.readObject(); } catch (ClassNotFoundException e) { throw new IOException(e); } - finally { - ois.close(); - } } static void assertPathEquals(final Path2D expectedPath, final Path2D actualPath) { PathIterator expectedIterator = expectedPath.getPathIterator(null); PathIterator actualIterator = actualPath.getPathIterator(null); + float[] expectedCoords = new float[6]; float[] actualCoords = new float[6]; - while(!actualIterator.isDone()) { + while(!expectedIterator.isDone()) { + assertFalse("Less points than expected", actualIterator.isDone()); + int expectedType = expectedIterator.currentSegment(expectedCoords); int actualType = actualIterator.currentSegment(actualCoords); - assertEquals(expectedType, actualType); - assertArrayEquals(expectedCoords, actualCoords, 0); + assertEquals("Unexpected segment type", expectedType, actualType); + assertArrayEquals("Unexpected coordinates", expectedCoords, actualCoords, 0); actualIterator.next(); expectedIterator.next(); } + + assertTrue("More points than expected", actualIterator.isDone()); } } diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDReader.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDReader.java index b20ace02..2c8f25db 100755 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDReader.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDReader.java @@ -83,7 +83,6 @@ public final class PSDReader extends MetadataReader { PSDResource resource = new PSDResource(id, input); entries.add(new PSDEntry(id, resource.name(), resource.data())); - } catch (EOFException e) { break;