segments;
+
+ /**
+ * 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.
+ *
+ *
+ * @param path A {@code Path2D} 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].
+ * @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].
+ */
+ public AdobePathWriter(final Path2D path) {
+ notNull(path, "path");
+ // TODO: Test if PS really ignores winding rule as documented... Otherwise we could support writing non-zero too.
+ isTrue(path.getWindingRule() == Path2D.WIND_EVEN_ODD, path.getWindingRule(), "Only even/odd winding rule supported: %d");
+ isTrue(new Rectangle(0, 0, 1, 1).contains(path.getBounds2D()), path.getBounds2D(), "Path bounds must be within [x=0,y=0,w=1,h=1]: %s");
+
+ segments = pathToSegments(path.getPathIterator(null));
+ }
+
+ // 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);
+
+ List subpath = new ArrayList<>();
+ List segments = new ArrayList<>();
+ segments.add(new AdobePathSegment(PATH_FILL_RULE_RECORD));
+
+ while (!pathIterator.isDone()) {
+ int segmentType = pathIterator.currentSegment(coords);
+ System.out.println("segmentType: " + segmentType);
+ System.err.println("coords: " + Arrays.toString(coords));
+
+ switch (segmentType) {
+ case PathIterator.SEG_MOVETO:
+ // TODO: What if we didn't close before the moveto? Start new segment here?
+
+ // Dummy starting point, will be updated later
+ prev = new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, 0, 0, coords[1], coords[0], 0, 0);
+ 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]));
+ 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]));
+ 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]));
+ prev = new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, coords[3], coords[2], coords[5], coords[4], 0, 0);
+ break;
+
+ case PathIterator.SEG_CLOSE:
+ // Replace initial point.
+ AdobePathSegment initial = subpath.get(0);
+ if (initial.apx != prev.apx || initial.apy != prev.apy) {
+ // TODO: Line back to initial if last anchor point does not equal initial anchor?
+// subpath.add(new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, prev.cppy, prev.cppx, prev.apy, prev.apx, 0, 0));
+ System.err.println("FOO!");
+ }
+ subpath.set(0, new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, 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);
+
+ subpath.clear();
+
+ break;
+ }
+
+ pathIterator.next();
+ }
+
+ return segments;
+ }
+
+ public void writePath(final DataOutput output) throws IOException {
+ System.err.println("segments: " + segments.size());
+
+ 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
+
+
+ for (AdobePathSegment segment : segments) {
+ System.err.println(segment);
+ switch (segment.selector) {
+ case PATH_FILL_RULE_RECORD:
+ case INITIAL_FILL_RULE_RECORD:
+ // The first 26-byte path record contains a selector value of 6, path fill rule record.
+ // The remaining 24 bytes of the first record are zeroes. Paths use even/odd ruling.
+ output.writeShort(segment.selector);
+ output.write(new byte[24]);
+ break;
+ case OPEN_SUBPATH_LENGTH_RECORD:
+ case CLOSED_SUBPATH_LENGTH_RECORD:
+ output.writeShort(segment.selector);
+ output.writeShort(segment.length); // Subpath length
+ output.write(new byte[22]);
+ break;
+ default:
+ output.writeShort(segment.selector);
+ output.writeInt(toFixedPoint(segment.cppy));
+ output.writeInt(toFixedPoint(segment.cppx));
+ output.writeInt(toFixedPoint(segment.apy));
+ output.writeInt(toFixedPoint(segment.apx));
+ output.writeInt(toFixedPoint(segment.cply));
+ output.writeInt(toFixedPoint(segment.cplx));
+ break;
+ }
+ }
+ }
+
+ public byte[] createPath() {
+ // 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) {
+ throw new AssertionError("Should never.. uh.. Oh well. It happened.", e);
+ }
+
+ return bytes.toByteArray();
+ }
+
+ // TODO: Move to AdobePathSegment
+ private static int toFixedPoint(final double value) {
+ return (int) Math.round(value * 0x1000000);
+ }
+}
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
old mode 100644
new mode 100755
index 8d30112a..e4d62c62
--- 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
@@ -69,7 +69,7 @@ import static java.util.Collections.singletonList;
*
*
* @see Adobe Photoshop Path resource format
- * @see com.twelvemonkeys.imageio.path.AdobePathBuilder
+ * @see AdobePathReader
* @author Jason Palmer, itemMaster LLC
* @author Harald Kuhr
* @author last modified by $Author: harald.kuhr$
@@ -90,7 +90,7 @@ public final class Paths {
* @throws javax.imageio.IIOException if the input contains a bad path data.
* @throws java.lang.IllegalArgumentException is {@code stream} is {@code null}.
*
- * @see com.twelvemonkeys.imageio.path.AdobePathBuilder
+ * @see AdobePathReader
*/
public static Path2D readPath(final ImageInputStream stream) throws IOException {
notNull(stream, "stream");
@@ -99,7 +99,7 @@ public final class Paths {
if (magic == PSD.RESOURCE_TYPE) {
// This is a PSD Image Resource Block, we can parse directly
- return buildPathFromPhotoshopResources(stream);
+ return readPathFromPhotoshopResources(stream);
}
else if (magic == PSD.SIGNATURE_8BPS) {
// PSD version
@@ -115,7 +115,7 @@ public final class Paths {
long imageResourcesLen = stream.readUnsignedInt();
// Image resources
- return buildPathFromPhotoshopResources(new SubImageInputStream(stream, imageResourcesLen));
+ return readPathFromPhotoshopResources(new SubImageInputStream(stream, imageResourcesLen));
}
else if (magic >>> 16 == JPEG.SOI && (magic & 0xff00) == 0xff00) {
// JPEG version
@@ -125,7 +125,7 @@ public final class Paths {
List photoshop = JPEGSegmentUtil.readSegments(stream, segmentIdentifiers);
if (!photoshop.isEmpty()) {
- return buildPathFromPhotoshopResources(new MemoryCacheImageInputStream(photoshop.get(0).data()));
+ return readPathFromPhotoshopResources(new MemoryCacheImageInputStream(photoshop.get(0).data()));
}
}
else if (magic >>> 16 == TIFF.BYTE_ORDER_MARK_BIG_ENDIAN && (magic & 0xffff) == TIFF.TIFF_MAGIC
@@ -137,7 +137,7 @@ public final class Paths {
Entry photoshop = directory.getEntryById(TIFF.TAG_PHOTOSHOP);
if (photoshop != null) {
- return buildPathFromPhotoshopResources(new ByteArrayImageInputStream((byte[]) photoshop.getValue()));
+ return readPathFromPhotoshopResources(new ByteArrayImageInputStream((byte[]) photoshop.getValue()));
}
}
@@ -156,17 +156,17 @@ public final class Paths {
}
}
- private static Path2D buildPathFromPhotoshopResources(final ImageInputStream stream) throws IOException {
+ private static Path2D readPathFromPhotoshopResources(final ImageInputStream stream) throws IOException {
Directory resourceBlocks = new PSDReader().read(stream);
- if (AdobePathBuilder.DEBUG) {
+ if (AdobePathReader.DEBUG) {
System.out.println("resourceBlocks: " + resourceBlocks);
}
Entry resourceBlock = resourceBlocks.getEntryById(PSD.RES_CLIPPING_PATH);
if (resourceBlock != null) {
- return new AdobePathBuilder((byte[]) resourceBlock.getValue()).path();
+ return new AdobePathReader((byte[]) resourceBlock.getValue()).path();
}
return null;
@@ -256,7 +256,19 @@ public final class Paths {
// Test code
public static void main(final String[] args) throws IOException, InterruptedException {
- BufferedImage destination = readClipped(ImageIO.createImageInputStream(new File(args[0])));
+ BufferedImage destination;
+ if (args.length == 1) {
+ // Embedded path
+ try (ImageInputStream input = ImageIO.createImageInputStream(new File(args[0]))) {
+ destination = readClipped(input);
+ }
+ }
+ else {
+ // Separate path and image
+ try (ImageInputStream input = ImageIO.createImageInputStream(new File(args[1]))) {
+ destination = applyClippingPath(readPath(input), ImageIO.read(new File(args[0])));
+ }
+ }
File tempFile = File.createTempFile("clipped-", ".png");
tempFile.deleteOnExit();