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 493305f8..f3f01baf 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 @@ -67,11 +67,12 @@ public final class AdobePathWriter { * regardless of image dimensions. *

* - * @param path A {@code Shape} instance that has {@link Path2D#WIND_EVEN_ODD WIND_EVEN_ODD} rule - * and is contained within the rectangle [x=0.0,y=0.0,w=1.0,h=1.0]. + * @param path A {@code Shape} instance that has {@link Path2D#WIND_EVEN_ODD WIND_EVEN_ODD} rule, + * is contained within the rectangle [x=0.0,y=0.0,w=1.0,h=1.0], and is closed. * @throws IllegalArgumentException if {@code path} is {@code null}, * the paths winding rule is not @link Path2D#WIND_EVEN_ODD} or - * the paths bounding box is outside [x=0.0,y=0.0,w=1.0,h=1.0]. + * the paths bounding box is outside [x=0.0,y=0.0,w=1.0,h=1.0] or + * the path is not closed. */ public AdobePathWriter(final Shape path) { notNull(path, "path"); @@ -128,8 +129,8 @@ public final class AdobePathWriter { break; case PathIterator.SEG_CLOSE: - // Replace initial point. AdobePathSegment initial = subpath.get(0); + if (initial.apx != prev.apx || initial.apy != prev.apy) { // Line back to initial if last anchor point does not equal initial anchor collinear = isCollinear(prev.cppx, prev.cppy, initial.apx, initial.apy, initial.apx, initial.apy); @@ -137,13 +138,7 @@ public final class AdobePathWriter { prev = new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, initial.apy, initial.apx, initial.apy, initial.apx, 0, 0); } - collinear = isCollinear(prev.cppx, prev.cppy, initial.apx, initial.apy, initial.cplx, initial.cply); - 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())); - segments.addAll(subpath); - + close(initial, prev, subpath, segments); subpath.clear(); break; @@ -152,12 +147,31 @@ public final class AdobePathWriter { pathIterator.next(); } - // TODO: If subpath is not empty at this point, there was no close segment... - // Either wrap up (if coordinates match), or throw exception (otherwise) + // If subpath is not empty at this point, there was no close segment... + // Wrap up if coordinates match, otherwise throw exception + if (!subpath.isEmpty()) { + AdobePathSegment initial = subpath.get(0); + + if (initial.apx != prev.apx || initial.apy != prev.apy) { + throw new IllegalArgumentException("Path must be closed"); + } + + close(initial, prev, subpath, segments); + } return segments; } + private static void close(AdobePathSegment initial, AdobePathSegment prev, List subpath, List segments) { + // Replace initial point. + boolean collinear = isCollinear(prev.cppx, prev.cppy, initial.apx, initial.apy, initial.cplx, initial.cply); + 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())); + segments.addAll(subpath); + } + private static boolean isCollinear(double x1, double y1, double x2, double y2, double x3, double y3) { // Photoshop seems to write as linked if all points are the same.... return (x1 == x2 && x2 == x3 && y1 == y2 && y2 == y3) || @@ -248,5 +262,4 @@ public final class AdobePathWriter { return bytes.toByteArray(); } - } 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 4562dd67..ce95a4f8 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 @@ -53,6 +53,7 @@ import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; import java.awt.image.BufferedImage; +import java.awt.image.RenderedImage; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -258,7 +259,30 @@ public final class Paths { return applyClippingPath(clip, image); } - public static boolean writeClipped(final BufferedImage image, Shape clipPath, final String formatName, final ImageOutputStream output) throws IOException { + /** + * Writes the image along with a clipping path resource, in the given format to the supplied output. + * The image is written to the + * {@code ImageOutputStream} starting at the current stream + * pointer, overwriting existing stream data from that point + * forward, if present. + *

+ * Note: As {@link ImageIO#write(RenderedImage, String, ImageOutputStream)}, this method does + * not close the output stream. + * It is the responsibility of the caller to close the stream, if desired. + *

+ * + * @param image the image to be written, may not be {@code null}. + * @param clipPath the clip path, may not be {@code null}. + * @param formatName the informal format name, may not be {@code null}. + * @param output the stream to write to, may not be {@code null}. + * + * @return {@code true} if the image was written, + * otherwise {@code false} (ie. no writer was found for the specified format). + * + * @exception IllegalArgumentException if any parameter is {@code null}. + * @exception IOException if an error occurs during writing. + */ + public static boolean writeClipped(final RenderedImage image, Shape clipPath, final String formatName, final ImageOutputStream output) throws IOException { if (image == null) { throw new IllegalArgumentException("image == null!"); } @@ -269,74 +293,75 @@ public final class Paths { throw new IllegalArgumentException("output == null!"); } - String format = "JPG".equalsIgnoreCase(formatName) ? "JPEG" : formatName.toUpperCase(); + ImageTypeSpecifier type = ImageTypeSpecifier.createFromRenderedImage(image); + Iterator writers = ImageIO.getImageWriters(type, formatName); - if ("TIFF".equals(format) || "JPEG".equals(format)) { - ImageTypeSpecifier type = ImageTypeSpecifier.createFromRenderedImage(image); - Iterator writers = ImageIO.getImageWriters(type, formatName); + if (writers.hasNext()) { + ImageWriter writer = writers.next(); - if (writers.hasNext()) { - ImageWriter writer = writers.next(); + ImageWriteParam param = writer.getDefaultWriteParam(); + IIOMetadata metadata = writer.getDefaultImageMetadata(type, param); + List metadataFormats = asList(metadata.getMetadataFormatNames()); - ImageWriteParam param = writer.getDefaultWriteParam(); - IIOMetadata metadata = writer.getDefaultImageMetadata(type, param); - List metadataFormats = asList(metadata.getMetadataFormatNames()); + byte[] pathResource = new AdobePathWriter(clipPath).writePathResource(); - byte[] pathResource = new AdobePathWriter(clipPath).writePathResource(); + if (metadataFormats.contains("javax_imageio_tiff_image_1.0") || metadataFormats.contains("com_sun_media_imageio_plugins_tiff_image_1.0")) { + param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + param.setCompressionType("Deflate"); - if ("TIFF".equals(format)) { - param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); - param.setCompressionType("Deflate"); + // Check if the format is that of the bundled TIFF writer, otherwise use JAI format + String metadataFormat = metadataFormats.contains("javax_imageio_tiff_image_1.0") + ? "javax_imageio_tiff_image_1.0" + : "com_sun_media_imageio_plugins_tiff_image_1.0"; // Fails in mergeTree, if not supported + IIOMetadataNode root = new IIOMetadataNode(metadataFormat); + IIOMetadataNode ifd = new IIOMetadataNode("TIFFIFD"); - // Check if the format is that of the bundled TIFF writer, otherwise use JAI format - String metadataFormat = metadataFormats.contains("javax_imageio_tiff_image_1.0") - ? "javax_imageio_tiff_image_1.0" - : "com_sun_media_imageio_plugins_tiff_image_1.0"; // Fails in mergeTree, if not supported - IIOMetadataNode root = new IIOMetadataNode(metadataFormat); - IIOMetadataNode ifd = new IIOMetadataNode("TIFFIFD"); + IIOMetadataNode pathField = new IIOMetadataNode("TIFFField"); + pathField.setAttribute("number", String.valueOf(TIFF.TAG_PHOTOSHOP)); + IIOMetadataNode pathValue = new IIOMetadataNode("TIFFUndefined"); // Use undefined for simplicity, could also use bytes + pathValue.setAttribute("value", arrayAsString(pathResource)); - IIOMetadataNode pathField = new IIOMetadataNode("TIFFField"); - pathField.setAttribute("number", String.valueOf(TIFF.TAG_PHOTOSHOP)); - IIOMetadataNode pathValue = new IIOMetadataNode("TIFFUndefined"); // Use undefined for simplicity, could also use bytes - pathValue.setAttribute("value", arrayAsString(pathResource)); + pathField.appendChild(pathValue); + ifd.appendChild(pathField); + root.appendChild(ifd); - pathField.appendChild(pathValue); - ifd.appendChild(pathField); - root.appendChild(ifd); - - metadata.mergeTree(metadataFormat, root); - } - else if ("JPEG".equals(format)) { - String metadataFormat = "javax_imageio_jpeg_image_1.0"; - IIOMetadataNode root = new IIOMetadataNode(metadataFormat); - - root.appendChild(new IIOMetadataNode("JPEGvariety")); - - IIOMetadataNode sequence = new IIOMetadataNode("markerSequence"); - - // App13/Photshop 3.0 - IIOMetadataNode unknown = new IIOMetadataNode("unknown"); - unknown.setAttribute("MarkerTag", Integer.toString(JPEG.APP13 & 0xFF)); - - byte[] identfier = "Photoshop 3.0".getBytes(StandardCharsets.US_ASCII); - byte[] data = new byte[identfier.length + 1 + pathResource.length]; - System.arraycopy(identfier, 0, data, 0, identfier.length); - System.arraycopy(pathResource, 0, data, identfier.length + 1, pathResource.length); - - unknown.setUserObject(data); - - sequence.appendChild(unknown); - root.appendChild(sequence); - - metadata.mergeTree(metadataFormat, root); - } - // TODO: Else if PSD... Requires PSD write + new metadata format... + metadata.mergeTree(metadataFormat, root); writer.setOutput(output); writer.write(null, new IIOImage(image, null, metadata), param); return true; } + else if (metadataFormats.contains("javax_imageio_jpeg_image_1.0")) { + String metadataFormat = "javax_imageio_jpeg_image_1.0"; + IIOMetadataNode root = new IIOMetadataNode(metadataFormat); + + root.appendChild(new IIOMetadataNode("JPEGvariety")); + + IIOMetadataNode sequence = new IIOMetadataNode("markerSequence"); + + // App13/Photshop 3.0 + IIOMetadataNode unknown = new IIOMetadataNode("unknown"); + unknown.setAttribute("MarkerTag", Integer.toString(JPEG.APP13 & 0xFF)); + + byte[] identfier = "Photoshop 3.0".getBytes(StandardCharsets.US_ASCII); + byte[] data = new byte[identfier.length + 1 + pathResource.length]; + System.arraycopy(identfier, 0, data, 0, identfier.length); + System.arraycopy(pathResource, 0, data, identfier.length + 1, pathResource.length); + + unknown.setUserObject(data); + + sequence.appendChild(unknown); + root.appendChild(sequence); + + metadata.mergeTree(metadataFormat, root); + + writer.setOutput(output); + writer.write(null, new IIOImage(image, null, metadata), param); + + return true; + } + // TODO: Else if PSD... Requires PSD write + new metadata format... } return false; 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 index ad515e33..5179ed1e 100644 --- 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 @@ -44,7 +44,8 @@ import java.util.Arrays; import static com.twelvemonkeys.imageio.path.AdobePathSegment.*; import static com.twelvemonkeys.imageio.path.PathsTest.assertPathEquals; -import static org.junit.Assert.*; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; /** * AdobePathWriterTest. @@ -92,6 +93,16 @@ public class AdobePathWriterTest { new AdobePathWriter(path); } + @Test(expected = IllegalArgumentException.class) + public void testCreateNotClosed() { + GeneralPath path = new GeneralPath(Path2D.WIND_EVEN_ODD); + path.moveTo(.5, .5); + path.lineTo(1, .5); + path.curveTo(1, 1, 1, 1, .5, 1); + + new AdobePathWriter(path).writePath(); + } + @Test public void testCreateClosed() { GeneralPath path = new GeneralPath(Path2D.WIND_EVEN_ODD); @@ -100,9 +111,30 @@ public class AdobePathWriterTest { path.curveTo(1, 1, 1, 1, .5, 1); path.closePath(); - new AdobePathWriter(path).writePath(); + byte[] bytes = new AdobePathWriter(path).writePath(); - fail("Test that we have 4 segments"); + assertEquals(6 * 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, 3, 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, -128, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0, 0, -128, 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, 1, 0, 0, 0, 1, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Sanity + assertEquals(bytes.length, off); } @Test @@ -113,9 +145,31 @@ public class AdobePathWriterTest { path.curveTo(1, 1, 1, 1, .5, 1); path.lineTo(.5, .5); - new AdobePathWriter(path).writePath(); // TODO: Should we allow this? + byte[] bytes = new AdobePathWriter(path).writePath(); + + assertEquals(6 * 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, 3, 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, -128, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0, 0, -128, 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, 1, 0, 0, 0, 1, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Sanity + assertEquals(bytes.length, off); - fail("Test that we have 4 segments, and that it is equal to the one above"); } @Test @@ -127,9 +181,30 @@ public class AdobePathWriterTest { path.lineTo(.5, .5); path.closePath(); - new AdobePathWriter(path).writePath(); + byte[] bytes = new AdobePathWriter(path).writePath(); - fail("Test that we have 4 segments, and that it is equal to the one above"); + assertEquals(6 * 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, 3, 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, -128, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0, 0, -128, 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, 1, 0, 0, 0, 1, 0, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + assertArrayEquals(new byte[] {0, CLOSED_SUBPATH_BEZIER_UNLINKED, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0, 0, -128, 0, 0}, + Arrays.copyOfRange(bytes, off, off += 26)); + + // Sanity + assertEquals(bytes.length, off); } @Test @@ -207,7 +282,6 @@ public class AdobePathWriterTest { AdobePathWriter pathCreator = new AdobePathWriter(path); byte[] bytes = pathCreator.writePath(); -// System.err.println(Arrays.toString(bytes)); assertEquals(12 * 26, bytes.length); 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 e40e034b..9f5b373b 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 @@ -38,15 +38,18 @@ import org.junit.Test; import javax.imageio.ImageIO; import javax.imageio.spi.IIORegistry; import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.ImageOutputStream; import java.awt.*; import java.awt.geom.GeneralPath; import java.awt.geom.Path2D; import java.awt.geom.PathIterator; import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import static org.junit.Assert.*; +import static org.junit.Assume.assumeTrue; /** * PathsTest. @@ -240,6 +243,9 @@ public class PathsTest { } static void assertPathEquals(final Path2D expectedPath, final Path2D actualPath) { + assertNotNull("Expected path is null, check your tests...", expectedPath); + assertNotNull(actualPath); + PathIterator expectedIterator = expectedPath.getPathIterator(null); PathIterator actualIterator = actualPath.getPathIterator(null); @@ -261,4 +267,37 @@ public class PathsTest { assertTrue("More points than expected", actualIterator.isDone()); } + + @Test + public void testWriteJPEG() throws IOException { + Path2D originalPath = readExpectedPath("/ser/multiple-clips.ser"); + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + BufferedImage image = new BufferedImage(2, 2, BufferedImage.TYPE_3BYTE_BGR); + try (ImageOutputStream stream = ImageIO.createImageOutputStream(bytes)) { + boolean written = Paths.writeClipped(image, originalPath, "JPEG", stream); + assertTrue(written); + } + assertTrue(bytes.size() > 1024); // Actual size may be plugin specific... + + Path2D actualPath = Paths.readPath(new ByteArrayImageInputStream(bytes.toByteArray())); + assertPathEquals(originalPath, actualPath); + } + + @Test + public void testWriteTIFF() throws IOException { + Path2D originalPath = readExpectedPath("/ser/grape-path.ser"); + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + BufferedImage image = new BufferedImage(2, 2, BufferedImage.TYPE_INT_ARGB); + try (ImageOutputStream stream = ImageIO.createImageOutputStream(bytes)) { + boolean written = Paths.writeClipped(image, originalPath, "TIFF", stream); + assumeTrue(written); // TIFF support is optional + } + + assertTrue(bytes.size() > 1024); // Actual size may be plugin specific... + + Path2D actualPath = Paths.readPath(new ByteArrayImageInputStream(bytes.toByteArray())); + assertPathEquals(originalPath, actualPath); + } }