mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-05 12:35:29 -04:00
#490: Initial commit Adobe Path write.
This commit is contained in:
parent
5501c0e709
commit
859b232f64
236
imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathBuilder.java
Normal file → Executable file
236
imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathBuilder.java
Normal file → Executable file
@ -1,244 +1,26 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2014, 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;
|
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.awt.geom.Path2D;
|
||||||
import java.io.DataInput;
|
import java.io.DataInput;
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static com.twelvemonkeys.lang.Validate.isTrue;
|
|
||||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code Shape} object from an Adobe Photoshop Path resource.
|
* AdobePathBuilder.
|
||||||
*
|
*
|
||||||
* @see <a href="http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_17587">Adobe Photoshop Path resource format</a>
|
* @deprecated Use {@link AdobePathReader} instead. This class will be removed in a future release.
|
||||||
* @author <a href="mailto:jpalmer@itemmaster.com">Jason Palmer, itemMaster LLC</a>
|
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
|
||||||
*/
|
*/
|
||||||
public final class AdobePathBuilder {
|
public final class AdobePathBuilder {
|
||||||
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.path.debug"));
|
private final AdobePathReader delegate;
|
||||||
|
|
||||||
private final DataInput data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a path builder that will read its data from a {@code DataInput}, such as an
|
|
||||||
* {@code ImageInputStream}.
|
|
||||||
* The data length is assumed to be a multiple of 26.
|
|
||||||
*
|
|
||||||
* @param data the input to read data from.
|
|
||||||
* @throws java.lang.IllegalArgumentException if {@code data} is {@code null}
|
|
||||||
*/
|
|
||||||
public AdobePathBuilder(final DataInput data) {
|
|
||||||
notNull(data, "data");
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a path builder that will read its data from a {@code byte} array.
|
|
||||||
* The array length must be a multiple of 26, and greater than 0.
|
|
||||||
*
|
|
||||||
* @param data the array to read data from.
|
|
||||||
* @throws java.lang.IllegalArgumentException if {@code data} is {@code null}, or not a multiple of 26.
|
|
||||||
*/
|
|
||||||
public AdobePathBuilder(final byte[] data) {
|
public AdobePathBuilder(final byte[] data) {
|
||||||
this(new ByteArrayImageInputStream(
|
this.delegate = new AdobePathReader(data);
|
||||||
notNull(data, "data"), 0,
|
}
|
||||||
isTrue(data.length > 0 && data.length % 26 == 0, data.length, "data.length must be a multiple of 26: %d")
|
|
||||||
));
|
public AdobePathBuilder(final DataInput data) {
|
||||||
|
this.delegate = new AdobePathReader(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the path.
|
|
||||||
*
|
|
||||||
* @return the path
|
|
||||||
* @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 path() throws IOException {
|
||||||
List<List<AdobePathSegment>> subPaths = new ArrayList<List<AdobePathSegment>>();
|
return delegate.path();
|
||||||
List<AdobePathSegment> currentPath = null;
|
|
||||||
int currentPathLength = 0;
|
|
||||||
|
|
||||||
AdobePathSegment segment;
|
|
||||||
while ((segment = nextSegment()) != null) {
|
|
||||||
|
|
||||||
if (DEBUG) {
|
|
||||||
System.out.println(segment);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (segment.selector == AdobePathSegment.OPEN_SUBPATH_LENGTH_RECORD || segment.selector == AdobePathSegment.CLOSED_SUBPATH_LENGTH_RECORD) {
|
|
||||||
if (currentPath != null) {
|
|
||||||
if (currentPathLength != currentPath.size()) {
|
|
||||||
throw new IIOException(String.format("Bad path, expected %d segments, found only %d", currentPathLength, currentPath.size()));
|
|
||||||
}
|
|
||||||
subPaths.add(currentPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPath = new ArrayList<AdobePathSegment>(segment.length);
|
|
||||||
currentPathLength = segment.length;
|
|
||||||
}
|
|
||||||
else if (segment.selector == AdobePathSegment.OPEN_SUBPATH_BEZIER_LINKED
|
|
||||||
|| segment.selector == AdobePathSegment.OPEN_SUBPATH_BEZIER_UNLINKED
|
|
||||||
|| segment.selector == AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED
|
|
||||||
|| segment.selector == AdobePathSegment.CLOSED_SUBPATH_BEZIER_UNLINKED) {
|
|
||||||
if (currentPath == null) {
|
|
||||||
throw new IIOException("Bad path, missing subpath length record");
|
|
||||||
}
|
|
||||||
if (currentPath.size() >= currentPathLength) {
|
|
||||||
throw new IIOException(String.format("Bad path, expected %d segments, found%d", currentPathLength, currentPath.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPath.add(segment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// now add the last one
|
|
||||||
if (currentPath != null) {
|
|
||||||
if (currentPathLength != currentPath.size()) {
|
|
||||||
throw new IIOException(String.format("Bad path, expected %d segments, found only %d", currentPathLength, currentPath.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
subPaths.add(currentPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// now we have collected the PathPoints now create a Shape.
|
|
||||||
return pathToShape(subPaths);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Correct Order... P1, P2, P3, P4, P5, P6 (Closed) moveTo(P1)
|
|
||||||
* curveTo(P1.cpl, P2.cpp, P2.ap); curveTo(P2.cpl, P3.cppy, P3.ap);
|
|
||||||
* curveTo(P3.cpl, P4.cpp, P4.ap); curveTo(P4.cpl, P5.cpp, P5.ap);
|
|
||||||
* curveTo(P5.cply, P6.cpp, P6.ap); curveTo(P6.cpl, P1.cpp, P1.ap);
|
|
||||||
* closePath()
|
|
||||||
*/
|
|
||||||
private Path2D pathToShape(final List<List<AdobePathSegment>> paths) {
|
|
||||||
GeneralPath path = new GeneralPath(Path2D.WIND_EVEN_ODD, paths.size());
|
|
||||||
GeneralPath subpath = null;
|
|
||||||
|
|
||||||
for (List<AdobePathSegment> points : paths) {
|
|
||||||
int length = points.size();
|
|
||||||
|
|
||||||
for (int i = 0; i < points.size(); i++) {
|
|
||||||
AdobePathSegment current = points.get(i);
|
|
||||||
|
|
||||||
int step = i == 0 ? 0 : i == length - 1 ? 2 : 1;
|
|
||||||
|
|
||||||
switch (step) {
|
|
||||||
// begin
|
|
||||||
case 0: {
|
|
||||||
subpath = new GeneralPath(Path2D.WIND_EVEN_ODD, length);
|
|
||||||
subpath.moveTo(current.apx, current.apy);
|
|
||||||
|
|
||||||
if (length > 1) {
|
|
||||||
AdobePathSegment next = points.get((i + 1));
|
|
||||||
subpath.curveTo(current.cplx, current.cply, next.cppx, next.cppy, next.apx, next.apy);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
subpath.lineTo(current.apx, current.apy);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// middle
|
|
||||||
case 1: {
|
|
||||||
AdobePathSegment next = points.get((i + 1)); // we are always guaranteed one more.
|
|
||||||
subpath.curveTo(current.cplx, current.cply, next.cppx, next.cppy, next.apx, next.apy);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// end
|
|
||||||
case 2: {
|
|
||||||
AdobePathSegment first = points.get(0);
|
|
||||||
|
|
||||||
if (first.selector == AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED || first.selector == AdobePathSegment.CLOSED_SUBPATH_BEZIER_UNLINKED) {
|
|
||||||
subpath.curveTo(current.cplx, current.cply, first.cppx, first.cppy, first.apx, first.apy);
|
|
||||||
subpath.closePath();
|
|
||||||
path.append(subpath, false);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
subpath.lineTo(current.apx, current.apy);
|
|
||||||
path.append(subpath, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AdobePathSegment nextSegment() throws IOException {
|
|
||||||
// Each segment is 26 bytes
|
|
||||||
int selector;
|
|
||||||
try {
|
|
||||||
selector = data.readUnsignedShort();
|
|
||||||
}
|
|
||||||
catch (EOFException eof) {
|
|
||||||
// No more data, we're done
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spec says Fill rule is ignored by Photoshop... Probably not.. ;-)
|
|
||||||
// TODO: Replace with switch + handle all types!
|
|
||||||
// TODO: ...or Move logic to AdobePathSegment?
|
|
||||||
if (selector == AdobePathSegment.CLOSED_SUBPATH_LENGTH_RECORD || selector == AdobePathSegment.OPEN_SUBPATH_LENGTH_RECORD) {
|
|
||||||
int size = data.readUnsignedShort();
|
|
||||||
// data.position(data.position() + 22); // Skip remaining
|
|
||||||
data.skipBytes(22);
|
|
||||||
return new AdobePathSegment(selector, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AdobePathSegment(
|
|
||||||
selector,
|
|
||||||
readFixedPoint(data.readInt()),
|
|
||||||
readFixedPoint(data.readInt()),
|
|
||||||
readFixedPoint(data.readInt()),
|
|
||||||
readFixedPoint(data.readInt()),
|
|
||||||
readFixedPoint(data.readInt()),
|
|
||||||
readFixedPoint(data.readInt())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static double readFixedPoint(final int fixed) {
|
|
||||||
return ((double) fixed / 0x1000000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,248 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2014, 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 javax.imageio.IIOException;
|
||||||
|
import java.awt.geom.GeneralPath;
|
||||||
|
import java.awt.geom.Path2D;
|
||||||
|
import java.io.DataInput;
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code Shape} object from an Adobe Photoshop Path resource.
|
||||||
|
*
|
||||||
|
* @see <a href="http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_17587">Adobe Photoshop Path resource format</a>
|
||||||
|
* @author <a href="mailto:jpalmer@itemmaster.com">Jason Palmer, itemMaster LLC</a>
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
*/
|
||||||
|
public final class AdobePathReader {
|
||||||
|
static final boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.path.debug"));
|
||||||
|
|
||||||
|
private final DataInput data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a path builder that will read its data from a {@code DataInput}, such as an
|
||||||
|
* {@code ImageInputStream}.
|
||||||
|
* The data length is assumed to be a multiple of 26.
|
||||||
|
*
|
||||||
|
* @param data the input to read data from.
|
||||||
|
* @throws java.lang.IllegalArgumentException if {@code data} is {@code null}
|
||||||
|
*/
|
||||||
|
public AdobePathReader(final DataInput data) {
|
||||||
|
notNull(data, "data");
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a path builder that will read its data from a {@code byte} array.
|
||||||
|
* The array length must be a multiple of 26, and greater than 0.
|
||||||
|
*
|
||||||
|
* @param data the array to read data from.
|
||||||
|
* @throws java.lang.IllegalArgumentException if {@code data} is {@code null}, or not a multiple of 26.
|
||||||
|
*/
|
||||||
|
public AdobePathReader(final byte[] data) {
|
||||||
|
this(new ByteArrayImageInputStream(
|
||||||
|
notNull(data, "data"), 0,
|
||||||
|
isTrue(data.length > 0 && data.length % 26 == 0, data.length, "data.length must be a multiple of 26: %d")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the path.
|
||||||
|
*
|
||||||
|
* @return the path
|
||||||
|
* @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 {
|
||||||
|
List<List<AdobePathSegment>> subPaths = new ArrayList<List<AdobePathSegment>>();
|
||||||
|
List<AdobePathSegment> currentPath = null;
|
||||||
|
int currentPathLength = 0;
|
||||||
|
|
||||||
|
AdobePathSegment segment;
|
||||||
|
while ((segment = nextSegment()) != null) {
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println(segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (segment.selector == AdobePathSegment.OPEN_SUBPATH_LENGTH_RECORD || segment.selector == AdobePathSegment.CLOSED_SUBPATH_LENGTH_RECORD) {
|
||||||
|
if (currentPath != null) {
|
||||||
|
if (currentPathLength != currentPath.size()) {
|
||||||
|
throw new IIOException(String.format("Bad path, expected %d segments, found only %d", currentPathLength, currentPath.size()));
|
||||||
|
}
|
||||||
|
subPaths.add(currentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPath = new ArrayList<AdobePathSegment>(segment.length);
|
||||||
|
currentPathLength = segment.length;
|
||||||
|
}
|
||||||
|
else if (segment.selector == AdobePathSegment.OPEN_SUBPATH_BEZIER_LINKED
|
||||||
|
|| segment.selector == AdobePathSegment.OPEN_SUBPATH_BEZIER_UNLINKED
|
||||||
|
|| segment.selector == AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED
|
||||||
|
|| segment.selector == AdobePathSegment.CLOSED_SUBPATH_BEZIER_UNLINKED) {
|
||||||
|
if (currentPath == null) {
|
||||||
|
throw new IIOException("Bad path, missing subpath length record");
|
||||||
|
}
|
||||||
|
if (currentPath.size() >= currentPathLength) {
|
||||||
|
throw new IIOException(String.format("Bad path, expected %d segments, found%d", currentPathLength, currentPath.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPath.add(segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now add the last one
|
||||||
|
if (currentPath != null) {
|
||||||
|
if (currentPathLength != currentPath.size()) {
|
||||||
|
throw new IIOException(String.format("Bad path, expected %d segments, found only %d", currentPathLength, currentPath.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
subPaths.add(currentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we have collected the PathPoints now create a Shape.
|
||||||
|
return pathToShape(subPaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Correct Order... P1, P2, P3, P4, P5, P6 (Closed) moveTo(P1)
|
||||||
|
* curveTo(P1.cpl, P2.cpp, P2.ap); curveTo(P2.cpl, P3.cpp, P3.ap);
|
||||||
|
* curveTo(P3.cpl, P4.cpp, P4.ap); curveTo(P4.cpl, P5.cpp, P5.ap);
|
||||||
|
* curveTo(P5.cpl, P6.cpp, P6.ap); curveTo(P6.cpl, P1.cpp, P1.ap);
|
||||||
|
* closePath()
|
||||||
|
*/
|
||||||
|
private Path2D pathToShape(final List<List<AdobePathSegment>> paths) {
|
||||||
|
GeneralPath path = new GeneralPath(Path2D.WIND_EVEN_ODD, paths.size());
|
||||||
|
GeneralPath subpath = null;
|
||||||
|
|
||||||
|
for (List<AdobePathSegment> points : paths) {
|
||||||
|
int length = points.size();
|
||||||
|
|
||||||
|
for (int i = 0; i < points.size(); i++) {
|
||||||
|
AdobePathSegment current = points.get(i);
|
||||||
|
|
||||||
|
int step = i == 0 ? 0 : i == length - 1 ? 2 : 1;
|
||||||
|
|
||||||
|
switch (step) {
|
||||||
|
// Begin
|
||||||
|
case 0: {
|
||||||
|
subpath = new GeneralPath(Path2D.WIND_EVEN_ODD, length);
|
||||||
|
subpath.moveTo(current.apx, current.apy);
|
||||||
|
|
||||||
|
if (length > 1) {
|
||||||
|
AdobePathSegment next = points.get((i + 1));
|
||||||
|
subpath.curveTo(current.cplx, current.cply, next.cppx, next.cppy, next.apx, next.apy);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
subpath.lineTo(current.apx, current.apy);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Middle
|
||||||
|
case 1: {
|
||||||
|
AdobePathSegment next = points.get((i + 1)); // We are always guaranteed one more.
|
||||||
|
subpath.curveTo(current.cplx, current.cply, next.cppx, next.cppy, next.apx, next.apy);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// End
|
||||||
|
case 2: {
|
||||||
|
AdobePathSegment first = points.get(0);
|
||||||
|
|
||||||
|
if (first.selector == AdobePathSegment.CLOSED_SUBPATH_BEZIER_LINKED || first.selector == AdobePathSegment.CLOSED_SUBPATH_BEZIER_UNLINKED) {
|
||||||
|
subpath.curveTo(current.cplx, current.cply, first.cppx, first.cppy, first.apx, first.apy);
|
||||||
|
subpath.closePath();
|
||||||
|
path.append(subpath, false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
subpath.lineTo(current.apx, current.apy);
|
||||||
|
path.append(subpath, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AdobePathSegment nextSegment() throws IOException {
|
||||||
|
// Each segment is 26 bytes
|
||||||
|
int selector;
|
||||||
|
try {
|
||||||
|
selector = data.readUnsignedShort();
|
||||||
|
}
|
||||||
|
catch (EOFException eof) {
|
||||||
|
// No more data, we're done
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
case AdobePathSegment.CLOSED_SUBPATH_LENGTH_RECORD:
|
||||||
|
case AdobePathSegment.OPEN_SUBPATH_LENGTH_RECORD:
|
||||||
|
int size = data.readUnsignedShort();
|
||||||
|
data.skipBytes(22);
|
||||||
|
return new AdobePathSegment(selector, size);
|
||||||
|
default:
|
||||||
|
return new AdobePathSegment(
|
||||||
|
selector,
|
||||||
|
toFixedPoint(data.readInt()),
|
||||||
|
toFixedPoint(data.readInt()),
|
||||||
|
toFixedPoint(data.readInt()),
|
||||||
|
toFixedPoint(data.readInt()),
|
||||||
|
toFixedPoint(data.readInt()),
|
||||||
|
toFixedPoint(data.readInt())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Move to AdobePathSegment
|
||||||
|
private static double toFixedPoint(final int fixed) {
|
||||||
|
return ((double) fixed / 0x1000000);
|
||||||
|
}
|
||||||
|
}
|
52
imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathSegment.java
Normal file → Executable file
52
imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/AdobePathSegment.java
Normal file → Executable file
@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.path;
|
package com.twelvemonkeys.imageio.path;
|
||||||
|
|
||||||
import com.twelvemonkeys.lang.Validate;
|
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adobe path segment.
|
* Adobe path segment.
|
||||||
@ -40,17 +40,17 @@ import com.twelvemonkeys.lang.Validate;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
*/
|
*/
|
||||||
final class AdobePathSegment {
|
final class AdobePathSegment {
|
||||||
public final static int CLOSED_SUBPATH_LENGTH_RECORD = 0;
|
static final int CLOSED_SUBPATH_LENGTH_RECORD = 0;
|
||||||
public final static int CLOSED_SUBPATH_BEZIER_LINKED = 1;
|
static final int CLOSED_SUBPATH_BEZIER_LINKED = 1;
|
||||||
public final static int CLOSED_SUBPATH_BEZIER_UNLINKED = 2;
|
static final int CLOSED_SUBPATH_BEZIER_UNLINKED = 2;
|
||||||
public final static int OPEN_SUBPATH_LENGTH_RECORD = 3;
|
static final int OPEN_SUBPATH_LENGTH_RECORD = 3;
|
||||||
public final static int OPEN_SUBPATH_BEZIER_LINKED = 4;
|
static final int OPEN_SUBPATH_BEZIER_LINKED = 4;
|
||||||
public final static int OPEN_SUBPATH_BEZIER_UNLINKED = 5;
|
static final int OPEN_SUBPATH_BEZIER_UNLINKED = 5;
|
||||||
public final static int PATH_FILL_RULE_RECORD = 6;
|
static final int PATH_FILL_RULE_RECORD = 6;
|
||||||
public final static int CLIPBOARD_RECORD = 7;
|
static final int CLIPBOARD_RECORD = 7;
|
||||||
public final static int INITIAL_FILL_RULE_RECORD = 8;
|
static final int INITIAL_FILL_RULE_RECORD = 8;
|
||||||
|
|
||||||
public final static String[] SELECTOR_NAMES = {
|
static final String[] SELECTOR_NAMES = {
|
||||||
"Closed subpath length record",
|
"Closed subpath length record",
|
||||||
"Closed subpath Bezier knot, linked",
|
"Closed subpath Bezier knot, linked",
|
||||||
"Closed subpath Bezier knot, unlinked",
|
"Closed subpath Bezier knot, unlinked",
|
||||||
@ -65,10 +65,16 @@ final class AdobePathSegment {
|
|||||||
final int selector;
|
final int selector;
|
||||||
final int length;
|
final int length;
|
||||||
|
|
||||||
|
// TODO: Consider keeping these in 8.24FP format
|
||||||
|
// Control point preceding knot
|
||||||
final double cppy;
|
final double cppy;
|
||||||
final double cppx;
|
final double cppx;
|
||||||
|
|
||||||
|
// Anchor point
|
||||||
final double apy;
|
final double apy;
|
||||||
final double apx;
|
final double apx;
|
||||||
|
|
||||||
|
// Control point leaving knot
|
||||||
final double cply;
|
final double cply;
|
||||||
final double cplx;
|
final double cplx;
|
||||||
|
|
||||||
@ -79,8 +85,15 @@ final class AdobePathSegment {
|
|||||||
this(selector, -1, cppy, cppx, apy, apx, cply, cplx);
|
this(selector, -1, cppy, cppx, apy, apx, cply, cplx);
|
||||||
}
|
}
|
||||||
|
|
||||||
AdobePathSegment(final int selector, final int length) {
|
AdobePathSegment(int fillRuleSelector) {
|
||||||
this(selector, length, -1, -1, -1, -1, -1, -1);
|
this(isTrue(fillRuleSelector == PATH_FILL_RULE_RECORD, fillRuleSelector, "Expected fill rule record (6): %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,
|
||||||
|
-1, -1, -1, -1, -1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AdobePathSegment(final int selector, final int length,
|
private AdobePathSegment(final int selector, final int length,
|
||||||
@ -91,15 +104,15 @@ final class AdobePathSegment {
|
|||||||
switch (selector) {
|
switch (selector) {
|
||||||
case CLOSED_SUBPATH_LENGTH_RECORD:
|
case CLOSED_SUBPATH_LENGTH_RECORD:
|
||||||
case OPEN_SUBPATH_LENGTH_RECORD:
|
case OPEN_SUBPATH_LENGTH_RECORD:
|
||||||
Validate.isTrue(length >= 0, length, "Bad size: %d");
|
isTrue(length >= 0, length, "Expected positive length: %d");
|
||||||
break;
|
break;
|
||||||
case CLOSED_SUBPATH_BEZIER_LINKED:
|
case CLOSED_SUBPATH_BEZIER_LINKED:
|
||||||
case CLOSED_SUBPATH_BEZIER_UNLINKED:
|
case CLOSED_SUBPATH_BEZIER_UNLINKED:
|
||||||
case OPEN_SUBPATH_BEZIER_LINKED:
|
case OPEN_SUBPATH_BEZIER_LINKED:
|
||||||
case OPEN_SUBPATH_BEZIER_UNLINKED:
|
case OPEN_SUBPATH_BEZIER_UNLINKED:
|
||||||
Validate.isTrue(
|
isTrue(
|
||||||
cppx >= 0 && cppx <= 1 && cppy >= 0 && cppy <= 1,
|
cppx >= 0 && cppx <= 1 && cppy >= 0 && cppy <= 1,
|
||||||
String.format("Unexpected point: [%f, %f]", cppx ,cppy)
|
String.format("Expected point in range [0...1]: (%f, %f)", cppx ,cppy)
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case PATH_FILL_RULE_RECORD:
|
case PATH_FILL_RULE_RECORD:
|
||||||
@ -107,7 +120,7 @@ final class AdobePathSegment {
|
|||||||
case INITIAL_FILL_RULE_RECORD:
|
case INITIAL_FILL_RULE_RECORD:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Bad selector: " + selector);
|
throw new IllegalArgumentException("Unknown selector: " + selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selector = selector;
|
this.selector = selector;
|
||||||
@ -173,10 +186,11 @@ final class AdobePathSegment {
|
|||||||
return String.format("Rule(selector=%s, rule=%d)", SELECTOR_NAMES[selector], length);
|
return String.format("Rule(selector=%s, rule=%d)", SELECTOR_NAMES[selector], length);
|
||||||
case CLOSED_SUBPATH_LENGTH_RECORD:
|
case CLOSED_SUBPATH_LENGTH_RECORD:
|
||||||
case OPEN_SUBPATH_LENGTH_RECORD:
|
case OPEN_SUBPATH_LENGTH_RECORD:
|
||||||
return String.format("Len(selector=%s, totalPoints=%d)", SELECTOR_NAMES[selector], length);
|
return String.format("Len(selector=%s, length=%d)", SELECTOR_NAMES[selector], length);
|
||||||
default:
|
default:
|
||||||
// fall-through
|
// fall-through
|
||||||
}
|
}
|
||||||
return String.format("Pt(preX=%.3f, preY=%.3f, knotX=%.3f, knotY=%.3f, postX=%.3f, postY=%.3f, selector=%s)", cppx, cppy, apx, apy, cplx, cply, SELECTOR_NAMES[selector]);
|
|
||||||
|
return String.format("Pt(pre=(%.3f, %.3f), knot=(%.3f, %.3f), post=(%.3f, %.3f), selector=%s)", cppx, cppy, apx, apy, cplx, cply, SELECTOR_NAMES[selector]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,168 @@
|
|||||||
|
package com.twelvemonkeys.imageio.path;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.psd.PSD;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.geom.Path2D;
|
||||||
|
import java.awt.geom.PathIterator;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataOutput;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.path.AdobePathSegment.*;
|
||||||
|
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AdobePathWriter
|
||||||
|
*/
|
||||||
|
public final class AdobePathWriter {
|
||||||
|
|
||||||
|
private final List<AdobePathSegment> segments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an AdobePathWriter for the given path.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @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<List<AdobePathSegment>...
|
||||||
|
private static List<AdobePathSegment> pathToSegments(final PathIterator pathIterator) {
|
||||||
|
double[] coords = new double[6];
|
||||||
|
AdobePathSegment prev = new AdobePathSegment(CLOSED_SUBPATH_BEZIER_LINKED, 0, 0, 0,0, 0, 0);
|
||||||
|
|
||||||
|
List<AdobePathSegment> subpath = new ArrayList<>();
|
||||||
|
List<AdobePathSegment> 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);
|
||||||
|
}
|
||||||
|
}
|
32
imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/Paths.java
Normal file → Executable file
32
imageio/imageio-clippath/src/main/java/com/twelvemonkeys/imageio/path/Paths.java
Normal file → Executable file
@ -69,7 +69,7 @@ import static java.util.Collections.singletonList;
|
|||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @see <a href="http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_17587">Adobe Photoshop Path resource format</a>
|
* @see <a href="http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_17587">Adobe Photoshop Path resource format</a>
|
||||||
* @see com.twelvemonkeys.imageio.path.AdobePathBuilder
|
* @see AdobePathReader
|
||||||
* @author <a href="mailto:jpalmer@itemmaster.com">Jason Palmer, itemMaster LLC</a>
|
* @author <a href="mailto:jpalmer@itemmaster.com">Jason Palmer, itemMaster LLC</a>
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $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 javax.imageio.IIOException if the input contains a bad path data.
|
||||||
* @throws java.lang.IllegalArgumentException is {@code stream} is {@code null}.
|
* @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 {
|
public static Path2D readPath(final ImageInputStream stream) throws IOException {
|
||||||
notNull(stream, "stream");
|
notNull(stream, "stream");
|
||||||
@ -99,7 +99,7 @@ public final class Paths {
|
|||||||
|
|
||||||
if (magic == PSD.RESOURCE_TYPE) {
|
if (magic == PSD.RESOURCE_TYPE) {
|
||||||
// This is a PSD Image Resource Block, we can parse directly
|
// This is a PSD Image Resource Block, we can parse directly
|
||||||
return buildPathFromPhotoshopResources(stream);
|
return readPathFromPhotoshopResources(stream);
|
||||||
}
|
}
|
||||||
else if (magic == PSD.SIGNATURE_8BPS) {
|
else if (magic == PSD.SIGNATURE_8BPS) {
|
||||||
// PSD version
|
// PSD version
|
||||||
@ -115,7 +115,7 @@ public final class Paths {
|
|||||||
long imageResourcesLen = stream.readUnsignedInt();
|
long imageResourcesLen = stream.readUnsignedInt();
|
||||||
|
|
||||||
// Image resources
|
// Image resources
|
||||||
return buildPathFromPhotoshopResources(new SubImageInputStream(stream, imageResourcesLen));
|
return readPathFromPhotoshopResources(new SubImageInputStream(stream, imageResourcesLen));
|
||||||
}
|
}
|
||||||
else if (magic >>> 16 == JPEG.SOI && (magic & 0xff00) == 0xff00) {
|
else if (magic >>> 16 == JPEG.SOI && (magic & 0xff00) == 0xff00) {
|
||||||
// JPEG version
|
// JPEG version
|
||||||
@ -125,7 +125,7 @@ public final class Paths {
|
|||||||
List<JPEGSegment> photoshop = JPEGSegmentUtil.readSegments(stream, segmentIdentifiers);
|
List<JPEGSegment> photoshop = JPEGSegmentUtil.readSegments(stream, segmentIdentifiers);
|
||||||
|
|
||||||
if (!photoshop.isEmpty()) {
|
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
|
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);
|
Entry photoshop = directory.getEntryById(TIFF.TAG_PHOTOSHOP);
|
||||||
|
|
||||||
if (photoshop != null) {
|
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);
|
Directory resourceBlocks = new PSDReader().read(stream);
|
||||||
|
|
||||||
if (AdobePathBuilder.DEBUG) {
|
if (AdobePathReader.DEBUG) {
|
||||||
System.out.println("resourceBlocks: " + resourceBlocks);
|
System.out.println("resourceBlocks: " + resourceBlocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
Entry resourceBlock = resourceBlocks.getEntryById(PSD.RES_CLIPPING_PATH);
|
Entry resourceBlock = resourceBlocks.getEntryById(PSD.RES_CLIPPING_PATH);
|
||||||
|
|
||||||
if (resourceBlock != null) {
|
if (resourceBlock != null) {
|
||||||
return new AdobePathBuilder((byte[]) resourceBlock.getValue()).path();
|
return new AdobePathReader((byte[]) resourceBlock.getValue()).path();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -256,7 +256,19 @@ public final class Paths {
|
|||||||
|
|
||||||
// Test code
|
// Test code
|
||||||
public static void main(final String[] args) throws IOException, InterruptedException {
|
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");
|
File tempFile = File.createTempFile("clipped-", ".png");
|
||||||
tempFile.deleteOnExit();
|
tempFile.deleteOnExit();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user