Adding the twelvemonkeys-imageio sub-project

This commit is contained in:
Harald Kuhr
2009-09-03 20:49:59 +02:00
parent 2523f31a8c
commit 4e7316886b
304 changed files with 27557 additions and 0 deletions
@@ -0,0 +1,112 @@
/*
* Copyright (c) 2008, 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 "TwelveMonkeys" 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 OWNER 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.plugins.psd;
import java.awt.color.ColorSpace;
/**
* CMYKColorSpace
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: CMYKColorSpace.java,v 1.0 Apr 30, 2008 1:38:13 PM haraldk Exp$
*/
// TODO: Move to com.twlevemonkeys.image?
// TODO: Read a ICC CMYK profile from classpath resource (from ECI)? ISO coated?
final class CMYKColorSpace extends ColorSpace {
static final ColorSpace INSTANCE = new CMYKColorSpace();
final ColorSpace sRGB = getInstance(CS_sRGB);
CMYKColorSpace() {
super(ColorSpace.TYPE_CMYK, 4);
}
public static ColorSpace getInstance() {
return INSTANCE;
}
public float[] toRGB(float[] colorvalue) {
return new float[] {
(1 - colorvalue[0]) * (1 - colorvalue[3]),
(1 - colorvalue[1]) * (1 - colorvalue[3]),
(1 - colorvalue[2]) * (1 - colorvalue[3])
};
// TODO: Convert via CIEXYZ space using sRGB space, as suggested in docs
// return sRGB.fromCIEXYZ(toCIEXYZ(colorvalue));
}
public float[] fromRGB(float[] rgbvalue) {
// Compute CMY
float c = 1 - rgbvalue[0];
float m = 1 - rgbvalue[1];
float y = 1 - rgbvalue[2];
// Find K
float k = Math.min(c, Math.min(m, y));
// Convert to CMYK values
return new float[] {(c - k), (m - k), (y - k), k};
/*
http://www.velocityreviews.com/forums/t127265-rgb-to-cmyk.html
(Step 0: Normalize R,G, and B values to fit into range [0.0 ... 1.0], or
adapt the following matrix.)
Step 1: RGB to CMY
| C | | 1 | | R |
| M | = | 1 | - | G |
| Y | | 1 | | B |
Step 2: CMY to CMYK
| C' | | C | | min(C,M,Y) |
| M' | | M | | min(C,M,Y) |
| Y' | = | Y | - | min(C,M,Y) |
| K' | | min(C,M,Y) | | 0 |
Easier to calculate if K' is calculated first, because K' = min(C,M,Y):
| C' | | C | | K' |
| M' | | M | | K' |
| Y' | = | Y | - | K' |
| K' | | K'| | 0 |
*/
// return fromCIEXYZ(sRGB.toCIEXYZ(rgbvalue));
}
public float[] toCIEXYZ(float[] colorvalue) {
throw new UnsupportedOperationException("Method toCIEXYZ not implemented"); // TODO: Implement
}
public float[] fromCIEXYZ(float[] colorvalue) {
throw new UnsupportedOperationException("Method fromCIEXYZ not implemented"); // TODO: Implement
}
}
@@ -0,0 +1,78 @@
/*
* Copyright (c) 2008, 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 "TwelveMonkeys" 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 OWNER 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.plugins.psd;
import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.stream.ImageInputStream;
import java.awt.color.ICC_Profile;
import java.io.IOException;
import java.io.InputStream;
/**
* ICCProfile
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: ICCProfile.java,v 1.0 May 20, 2008 6:24:10 PM haraldk Exp$
*/
class ICCProfile extends PSDImageResource {
private ICC_Profile mProfile;
ICCProfile(final short pId, final ImageInputStream pInput) throws IOException {
super(pId, pInput);
}
@Override
protected void readData(ImageInputStream pInput) throws IOException {
InputStream stream = IIOUtil.createStreamAdapter(pInput, mSize);
try {
mProfile = ICC_Profile.getInstance(stream);
}
finally {
// Make sure stream has correct position after read
stream.close();
}
}
public ICC_Profile getProfile() {
return mProfile;
}
@Override
public String toString() {
StringBuilder builder = toStringBuilder();
builder.append(", profile: ").append(mProfile);
builder.append("]");
return builder.toString();
}
}
@@ -0,0 +1,544 @@
/*
* Copyright (c) 2008, 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 "TwelveMonkeys" 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 OWNER 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.plugins.psd;
/**
* PSD format constants.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSD.java,v 1.0 Apr 29, 2008 4:47:47 PM haraldk Exp$
*
* @see <a href="http://www.fileformat.info/format/psd/egff.htm">http://www.fileformat.info/format/psd/egff.htm</a>
*/
interface PSD {
/** PSD 2+ Native format (.PSD) identifier "8BPS" */
int SIGNATURE_8BPS = ('8' << 24) + ('B' << 16) + ('P' << 8) + 'S';
/** PSD 5+ Large Document Format (.PSB) identifier "8BPB" */
int SIGNATURE_8BPB = ('8' << 24) + ('B' << 16) + ('P' << 8) + 'B';
/** PSD Resource type identifier "8BIM" */
int RESOURCE_TYPE = ('8' << 24) + ('B' << 16) + ('I' << 8) + 'M';
// Blending modes
/** Normal blending mode "norm"*/
int BLEND_NORM = ('n' << 24) + ('o' << 16) + ('r' << 8) + 'm';
/** Darken blending mode "dark" */
int BLEND_DARK = ('d' << 24) + ('a' << 16) + ('r' << 8) + 'k';
/** Lighten blending mode "lite" */
int BLEND_LITE = ('l' << 24) + ('i' << 16) + ('t' << 8) + 'e';
/** Hue blending mode "hue " */
int BLEND_HUE = ('h' << 24) + ('u' << 16) + ('e' << 8) + ' ';
/** Saturation blending mode "sat " */
int BLEND_SAT = ('s' << 24) + ('a' << 16) + ('t' << 8) + ' ';
/** Color blending mode "colr" */
int BLEND_COLR = ('c' << 24) + ('o' << 16) + ('l' << 8) + 'r';
/** Luminosity blending mode "lum " */
int BLEND_LUM = ('l' << 24) + ('u' << 16) + ('m' << 8) + ' ';
/** Multiply blending mode "mul " */
int BELND_MUL = ('m' << 24) + ('u' << 16) + ('l' << 8) + ' ';
/** Screen blending mode "scrn" */
int BLEND_SCRN = ('s' << 24) + ('c' << 16) + ('r' << 8) + 'n';
/** Dissolve blending mode "diss" */
int BLEND_DISS = ('d' << 24) + ('i' << 16) + ('s' << 8) + 's';
/** Overlay blending mode "over" */
int BLEND_OVER = ('o' << 24) + ('v' << 16) + ('e' << 8) + 'r';
/** Hard light blending mode "hLit" */
int BLEND_HLIT = ('h' << 24) + ('L' << 16) + ('i' << 8) + 't';
/** Soft light blending mode "sLit" */
int BLEND_SLIT = ('s' << 24) + ('L' << 16) + ('i' << 8) + 't';
/** Difference blending mode "diff" */
int BLEND_DIFF = ('d' << 24) + ('i' << 16) + ('f' << 8) + 'f';
// Compression modes
/** No compression */
int COMPRESSION_NONE = 0;
/** PacBits RLE compression */
int COMPRESSION_RLE = 1;
/** ZIP compression */
int COMPRESSION_ZIP = 2;
/** ZIP compression with prediction */
int COMPRESSION_ZIP_PREDICTON = 3;
// Color Modes
/** Bitmap (monochrome) */
short COLOR_MODE_MONOCHROME = 0;
/** Gray-scale */
short COLOR_MODE_GRAYSCALE = 1;
/** Indexed color (palette color) */
short COLOR_MODE_INDEXED = 2;
/** RGB color */
short COLOR_MODE_RGB = 3;
/** CMYK color */
short COLOR_MODE_CMYK = 4;
/** Multichannel color */
short COLOR_MODE_MULTICHANNEL = 7;
/** Duotone (halftone) */
short COLOR_MODE_DUOTONE = 8;
/** Lab color */
short COLOR_MODE_LAB = 9;
// TODO: Consider moving these constants to PSDImageResource
// ID values 03e8, 03eb, 03ff, and 0403 are considered obsolete. Values 03e8 and 03eb are associated with
// Photoshop v2.0. The data format for values 03f2, 03f4-03fa, 03fc, 03fd, 0405-0bb7 is intentionally not
// documented by Adobe, or the data is missing.
// Please refer to the Adobe Photoshop SDK for information on obtaining the IPTC-NAA record 2 structure definition.
// WORD[5]
/** Channels, rows, columns, depth, and mode (Obsolete?Photoshop 2.0 only). */
int RES_CHANNELS_ROWS_COLUMNS_DEPTH_MODE = 0x03e8;
/** Optional Macintosh print manager information. */
int RES_MAC_PRINT_MANAGER_INFO = 0x03e9;
/** Indexed color table (Obsolete?Photoshop 2.0 only). */
int RES_INDEXED_COLOR_TABLE = 0x03eb;
/** Resolution information
// ID value 03ed indicates that the data is in the form of a ResolutionInfo structure:
//
// typedef struct _ResolutionInfo
// {
// LONG hRes; // Fixed-point number: pixels per inch
// WORD hResUnit; // 1=pixels per inch, 2=pixels per centimeter
// WORD WidthUnit; // 1=in, 2=cm, 3=pt, 4=picas, 5=columns
// LONG vRes; // Fixed-point number: pixels per inch/
// WORD vResUnit; // 1=pixels per inch, 2=pixels per centimeter
// WORD HeightUnit; // 1=in, 2=cm, 3=pt, 4=picas, 5=columns
// } RESOLUTIONINFO;
*/
int RES_RESOLUTION_INFO = 0x3ed;
/** Alpha channel names (Pascal-format strings) */
int RES_ALPHA_CHANNEL_INFO = 0x3ee;
/** Display information for each channel
// ID value 03ef indicates that the data is stored as a DisplayInfo structure, which contains display information
// associated with each channel:
//
// typedef _DisplayInfo
// {
// WORD ColorSpace;
// WORD Color[4];
// WORD Opacity; // 0-100
// BYTE Kind; // 0=selected, 1=protected
// BYTE Padding; // Always zero
// } DISPLAYINFO;
//
*/
int RES_DISPLAY_INFO = 0x3ef;
// 03f0
// BYTE[]
/** Optional Pascal-format caption string */
int RES_CAPTION = 0x03f0;
// 03f1
// LONG, WORD
/** Fixed-point border width, border units */
int RES_BORDER_WIDTH = 0x03f1;
// 03f2
/** Background color */
// 2 byte Color space: 0 = RGB (unsigned 16 bit), 1 = HSB (unsigned 16 bit), 2 = CMYK (unsigned 16 bit),
// 3 = Pantone matching system (undocumented), 4 = Focoltone colour system (undocumented),
// 5 = Truematch color (undocumented), 6 = Toyo 88 colorfinder 1050 (undocumented),
// 7 = Lab (lighntess 0...10000, chrominance -12800..127000, 8 = Grayscale 0...10000,
// 10 = HKS colors
// 8 byte Color data: 6 first bytes used for RGB, HSB and Lab, all 8 for CMYK and only first two for grayscale
int RES_BACKGROUND_COLOR = 0x03f2;
// 03f3
// BYTE[8]
/**
* Print flags.
* ID value 03f3 indicates that the data is a series of eight flags, indicating the enabled state of labels,
* crop marks, color bars, registration marks, negative, flip, interpolate, and caption items in the
* Photoshop Page Setup dialog box.
*/
int RES_PRINT_FLAGS = 0x03f3;
// 03f4
/** Gray-scale and halftoning information */
int RES_GRAYSCALE_HALFTONE_INFO = 0x03f4;
// 03f5
/** Color halftoning information */
int RES_COLOR_HALFTONE_INFO = 0x03f5;
// 03f6
/** Duotone halftoning information */
int RES_DUOTONE_HALFTONE_INFO = 0x03f6;
// 03f7
/** Gray-scale and multichannel transfer function */
int RES_GRAYSCALE_MULTICHANNEL_TRANSFER_FUNCTION = 0x03f7;
// 03f8
/** Color transfer functions */
int RES_COLOR_TRANSFER_FUNCITON = 0x03f8;
// 03f9
/** Duotone transfer functions */
int RES_DUOTONE_TRANSFER_FUNCITON = 0x03f9;
// 03fa
/** Duotone image information */
int RES_DUOTONE_IMAGE_INFO = 0x03fa;
// 03fb
// BYTE[2]
/** Effective black and white value for dot range */
int RES_EFFECTIVE_BLACK_WHITE = 0x03fb;
// 03fc
/** Obsolete undocumented resource. */
int RES_03FC = 0x03fc;
// 03fd
/** EPS options */
int RES_EPS_OPTIONS = 0x03fd;
// 03fe
// WORD, BYTE
/** Quick Mask channel ID, flag for mask initially empty */
int RES_QUICK_MASK_CHANNEL_ID = 0x03fe;
// 03ff
/** Obsolete undocumented resource. */
int RES_03ff = 0x03ff;
// 0400
// WORD
/** Index of target layer (0=bottom)*/
int RES_INDEX_OF_TARGET_LAYER = 0x0400;
// 0401
/** Working path */
int RES_WORKING_PATH = 0x0401;
// 0402
// WORD[]
/** Layers group info, group ID for dragging groups */
int RES_LAYERS_GROUP_INFO = 0x0402;
//
// 0403
/** Obsolete undocumented resource. */
int RES_0403 = 0x0403;
// 0404
/** IPTC-NAA record */
int RES_IPTC_NAA = 0x0404;
// 0405
/** Image mode for raw-format files */
int RES_RAW_IMAGE_MODE = 0x0405;
// 0406
/** JPEG quality (Adobe internal) */
int RES_JPEG_QUALITY = 0x0406;
// 1032
/** (Photoshop 4.0) Grid and guides information */
int RES_GRID_AND_GUIDES_INFO = 0x0408;
// 1033
/**
* (Photoshop 4.0) Thumbnail resource for Photoshop 4.0 only. BGR layout. Obsolete.
* @see #RES_THUMBNAIL
*/
int RES_THUMBNAIL_PS4 = 0x0409;
// 1034
/**
* (Photoshop 4.0) Copyright flag
* Boolean indicating whether image is copyrighted. Can be set via
* Property suite or by user in File Info...
*/
int RES_COPYRIGHT_FLAG = 0x040A;
// 1035
/**
* (Photoshop 4.0) URL
* Handle of a text string with uniform resource locator. Can be set via
* Property suite or by user in File Info...
*/
int RES_URL = 0x040B;
// 1036
/** (Photoshop 5.0) Thumbnail resource (supersedes resource 1033) */
int RES_THUMBNAIL = 0x040C;
// 1037
/**
* (Photoshop 5.0) Global Angle
* 4 bytes that contain an integer between 0 and 359, which is the global
* lighting angle for effects layer. If not present, assumed to be 30.
*/
int RES_GLOBAL_ANGLE = 0x040D;
// 1038
/**
* (Photoshop 5.0) Color samplers resource
* See "Color samplers resource format" on page20.
*/
int RES_COLOR_SAMPLERS = 0x040E;
/**
* (Photoshop 5.0) ICC Profile
* The raw bytes of an ICC (International Color Consortium) format profile.
*/
int RES_ICC_PROFILE = 0x040f;
// 1040
/**
* (Photoshop 5.0) Watermark
* One byte.
*/
int RES_WATERMARK = 0x0410;
// 1041
/**
* (Photoshop 5.0) ICC Untagged Profile
* 1 byte that disables any assumed profile handling when opening the file.
* 1 = intentionally untagged.
*/
int RES_ICC_UNTAGGED_PROFILE = 0x0411;
// 1042
/**
* (Photoshop 5.0) Effects visible
* 1-byte global flag to show/hide all the effects layer. Only present when
* they are hidden.
*/
int RES_EFFECTS_VISIBLE = 0x0412;
// 1043
/**
* (Photoshop 5.0) Spot Halftone
* 4 bytes for version, 4 bytes for length, and the variable length data.
*/
int RES_SPOT_HALFTONE = 0x0413;
// 1044
/**
* (Photoshop 5.0) Document-specific IDs seed number
* 4 bytes: Base value, starting at which layer IDs will be generated (or a
* greater value if existing IDs already exceed it). Its purpose is to avoid the
* case where we add layers, flatten, save, open, and then add more layers
* that end up with the same IDs as the first set.
*/
int RES_DOC_ID_SEED = 0x0414;
// 1045
/**
* (Photoshop 5.0) Unicode Alpha Names
* Unicode string (4 bytes length followed by string).
*/
int RES_UNICODE_ALPHA_NAME = 0x0415;
// 1046
/**
* (Photoshop 6.0) Indexed Color Table Count
* 2 bytes for the number of colors in table that are actually defined
*/
int RES_INDEXED_COLOR_TABLE_COUNT = 0x0416;
//1047
/**
* (Photoshop 6.0) Transparency Index.
* 2 bytes for the index of transparent color, if any.
*/
int RES_TRANSPARENCY_INDEX = 0x0417;
//1049
/**
* (Photoshop 6.0) Global Altitude
* 4 byte entry for altitude
*/
int RES_GLOBAL_ALTITUDE = 0x0419;
//1050
/**
* (Photoshop 6.0) Slices
*/
int RES_SLICES = 0x041A;
//1051
/**
* (Photoshop 6.0) Workflow URL
* Unicode string
*/
int RES_WORKFLOW_URL = 0x041B;
// 1052
/**
* (Photoshop 6.0) Jump To XPEP
* 2 bytes major version, 2 bytes minor version, 4 bytes count. Following is
* repeated for count: 4 bytes block size, 4 bytes key, if key = 'jtDd', then
* next is a Boolean for the dirty flag; otherwise its a 4 byte entry for the
* mod date.
*/
int RES_JUMP_TO_XPEP = 0x041C;
// 1053
/**
* (Photoshop 6.0) Alpha Identifiers
* 4 bytes of length, followed by 4 bytes each for every alpha identifier.
*/
int RES_ALPHA_IDENTIFIERS = 0x041D;
// 1054
/**
* (Photoshop 6.0) URL List
* 4 byte count of URLs, followed by 4 byte long, 4 byte ID, and Unicode
* string for each count.
*/
int RES_URL_LIST = 0x041E;
// 1057
/**
* (Photoshop 6.0) Version Info
* 4 bytes version, 1 byte hasRealMergedData, Unicode string: writer
* name, Unicode string: reader name, 4 bytes file version.
*/
int RES_VERSION_INFO = 0x0421;
// 1058
/**
* (Photoshop 7.0) EXIF data 1
*
* @see <a href="http://www.pima.net/standards/it10/PIMA15740/exif.htm">EXIF standard</a>
*/
int RES_EXIF_DATA_1 = 0x0422;
//1059
/**
* (Photoshop 7.0) EXIF data 3
*
* @see <a href="http://www.pima.net/standards/it10/PIMA15740/exif.htm">EXIF standard</a>
*/
int RES_EXIF_DATA_3 = 0x0423;
//1060
/**
* (Photoshop 7.0) XMP metadata
* File info as XML description.
*
* @see <a href="http://Partners.adobe.com/asn/developer/xmp/main.html">XMP standard</a>
*/
int RES_XMP_DATA = 0x0424;
// 1061
/**
* (Photoshop 7.0) Caption digest
* 16 bytes: RSA Data Security, MD5 message-digest algorithm
*/
int RES_CAPTION_DIGEST = 0x0425;
// 1062
/**
* (Photoshop 7.0) Print scale
* 2 bytes style (0 = centered, 1 = size to fit, 2 = user defined). 4 bytes x
* location (floating point). 4 bytes y location (floating point). 4 bytes scale
* (floating point)
*/
int RES_PRINT_SCALE = 0x0426;
// 1064
/**
* (Photoshop CS) Pixel Aspect Ratio
* 4 bytes (version = 1), 8 bytes double, x / y of a pixel
* 0x0429 1065 (Photoshop CS) Layer Comps
* 4 bytes (descriptor version = 16), Descriptor (see ?Descriptor structure?
* on page57)
*/
int RES_PIXEL_ASPECT_RATIO = 0x0428;
// 1066
/**
* (Photoshop CS) Alternate Duotone Colors
* 2 bytes (version = 1), 2 bytes count, following is repeated for each count:
* [ Color: 2 bytes for space followed by 4 * 2 byte color component ],
* following this is another 2 byte count, usually 256, followed by Lab colors
* one byte each for L, a, b
* This resource is not read or used by Photoshop.
*/
int RES_ALTERNATE_DUOTONE_COLORS = 0x042A;
// 1067
/**
* (Photoshop CS) Alternate Spot Colors
* 2 bytes (version = 1), 2 bytes channel count, following is repeated for
* each count: 4 bytes channel ID, Color: 2 bytes for space followed by 4 * 2
* byte color component
* This resource is not read or used by Photoshop.
*/
int RES_ALTERNATE_SPOT_COLORS = 0x042B;
// 07d0-0bb6
/* Saved path information */
// 0bb7
/** Clipping path name */
int RES_CLIPPING_PATH_NAME = 0x0bb7;
// 2710
/** Print flags information
* ID value 2710 signals that the Data section contains a WORD-length version number (should be 1),
* a BYTE-length flag indicating crop marks, a BYTE-length field (should be 0), a LONG-length bleed width value, and a
* WORD indicating the bleed width scale.
*/
int RES_PRINT_FLAGS_INFORMATION = 0x2710;
}
@@ -0,0 +1,67 @@
/*
* Copyright (c) 2008, 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 "TwelveMonkeys" 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 OWNER 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.plugins.psd;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* PSDAlhpaChannelInfo
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDAlhpaChannelInfo.java,v 1.0 May 2, 2008 5:33:40 PM haraldk Exp$
*/
class PSDAlphaChannelInfo extends PSDImageResource {
List<String> mNames;
public PSDAlphaChannelInfo(short pId, final ImageInputStream pInput) throws IOException {
super(pId, pInput);
}
@Override
protected void readData(ImageInputStream pInput) throws IOException {
mNames = new ArrayList<String>();
long left = mSize;
while (left > 0) {
String name = PSDUtil.readPascalStringByte(pInput);
mNames.add(name);
left -= name.length() + 1;
}
}
@Override
public String toString() {
StringBuilder builder = toStringBuilder();
builder.append(", alpha channels: ").append(mNames).append("]");
return builder.toString();
}
}
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2008, 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 "TwelveMonkeys" 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 OWNER 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.plugins.psd;
/**
* PSDChannelInfo
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDChannelInfo.java,v 1.0 May 6, 2008 2:46:23 PM haraldk Exp$
*/
class PSDChannelInfo {
private short mChannelId;
long mLength;
// typedef struct _CLI
// {
// WORD ChannelID; /* Channel Length Info field one */
// LONG LengthOfChannelData; /* Channel Length Info field two */
// } CLI;
public PSDChannelInfo(short pChannelId, long pLength) {
mChannelId = pChannelId;
mLength = pLength;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(getClass().getSimpleName());
builder.append("[");
builder.append("channelId: ").append(mChannelId);
builder.append(", length: ").append(mLength);
builder.append("]");
return builder.toString();
}
}
@@ -0,0 +1,69 @@
/*
* Copyright (c) 2008, 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 "TwelveMonkeys" 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 OWNER 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.plugins.psd;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
/**
* PSDChannelSourceDestinationRange
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDChannelSourceDestinationRange.java,v 1.0 May 6, 2008 5:14:13 PM haraldk Exp$
*/
class PSDChannelSourceDestinationRange {
private String mChannel;
private short mSourceBlack;
private short mSourceWhite;
private short mDestBlack;
private short mDestWhite;
public PSDChannelSourceDestinationRange(ImageInputStream pInput, String pChannel) throws IOException {
mChannel = pChannel;
mSourceBlack = pInput.readShort();
mSourceWhite = pInput.readShort();
mDestBlack = pInput.readShort();
mDestWhite = pInput.readShort();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(getClass().getSimpleName());
builder.append("[(").append(mChannel);
builder.append("): sourceBlack: ").append(Integer.toHexString(mSourceBlack & 0xffff));
builder.append(", sourceWhite: ").append(Integer.toHexString(mSourceWhite & 0xffff));
builder.append(", destBlack: ").append(Integer.toHexString(mDestBlack & 0xffff));
builder.append(", destWhite: ").append(Integer.toHexString(mDestWhite & 0xffff));
builder.append("]");
return builder.toString();
}
}
@@ -0,0 +1,86 @@
/*
* Copyright (c) 2008, 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 "TwelveMonkeys" 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 OWNER 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.plugins.psd;
import com.twelvemonkeys.image.InverseColorMapIndexColorModel;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.io.IOException;
/**
* PSDColorData
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDColorData.java,v 1.0 Apr 29, 2008 5:33:01 PM haraldk Exp$
*/
class PSDColorData {
final byte[] mColors;
private IndexColorModel mColorModel;
PSDColorData(ImageInputStream pInput) throws IOException {
int length = pInput.readInt();
if (length == 0) {
throw new IIOException("No palette information in PSD");
}
else if (length % 3 != 0) {
throw new IIOException("Wrong palette information in PSD");
}
// NOTE: Spec says length may only be 768 bytes (256 RGB triplets)
mColors = new byte[length];
pInput.readFully(mColors);
// NOTE: Could be a padding byte here, if not even..
}
IndexColorModel getIndexColorModel() {
if (mColorModel == null) {
int[] rgb = toInterleavedRGB(mColors);
mColorModel = new InverseColorMapIndexColorModel(8, rgb.length, rgb, 0, false, -1, DataBuffer.TYPE_BYTE);
}
return mColorModel;
}
private int[] toInterleavedRGB(byte[] pColors) {
int[] rgb = new int[pColors.length / 3];
for (int i = 0; i < rgb.length; i++) {
// Pack the non-interleaved samples into interleaved form
int r = pColors[ i] & 0xff;
int g = pColors[ rgb.length + i] & 0xff;
int b = pColors[2 * rgb.length + i] & 0xff;
rgb[i] = (r << 16) | (g << 8) | b;
}
return rgb;
}
}
@@ -0,0 +1,120 @@
/*
* Copyright (c) 2008, 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 "TwelveMonkeys" 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 OWNER 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.plugins.psd;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.IIOException;
import java.io.IOException;
/**
* PSDResolutionInfo
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDResolutionInfo.java,v 1.0 May 2, 2008 3:58:19 PM haraldk Exp$
*/
class PSDDisplayInfo extends PSDImageResource {
// TODO: Size of this struct should be 14.. Does not compute...
//typedef _DisplayInfo
//{
// WORD ColorSpace;
// WORD Color[4];
// WORD Opacity; /* 0-100 */
// BYTE Kind; /* 0=selected, 1=protected */
// BYTE Padding; /* Always zero */
//} DISPLAYINFO;
private int mColorSpace;
private short[] mColors;
private short mOpacity;
private byte mKind;
PSDDisplayInfo(final short pId, final ImageInputStream pInput) throws IOException {
super(pId, pInput);
}
@Override
protected void readData(ImageInputStream pInput) throws IOException {
if (mSize % 14 != 0) {
throw new IIOException("Display info length expected to be mod 14: " + mSize);
}
// long left = mSize;
// while (left > 0) {
mColorSpace = pInput.readShort();
// Color[4]...?
mColors = new short[4];
mColors[0] = pInput.readShort();
mColors[1] = pInput.readShort();
mColors[2] = pInput.readShort();
mColors[3] = pInput.readShort();
mOpacity = pInput.readShort();
mKind = pInput.readByte();
pInput.readByte(); // Pad
// left -= 14;
// }
pInput.skipBytes(mSize - 14);
}
@Override
public String toString() {
StringBuilder builder = toStringBuilder();
builder.append(", ColorSpace: ").append(mColorSpace);
builder.append(", Colors: {");
builder.append(mColors[0]);
builder.append(", ");
builder.append(mColors[1]);
builder.append(", ");
builder.append(mColors[2]);
builder.append(", ");
builder.append(mColors[3]);
builder.append("}, Opacity: ").append(mOpacity);
builder.append(", Kind: ").append(kind(mKind));
builder.append("]");
return builder.toString();
}
private String kind(final byte pKind) {
switch (pKind) {
case 0:
return "selected";
case 1:
return "protected";
default:
return "unknown kind: " + Integer.toHexString(pKind & 0xff);
}
}
}
@@ -0,0 +1,327 @@
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.lang.StringUtil;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* EXIF metadata.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: XMPData.java,v 1.0 Jul 28, 2009 5:50:34 PM haraldk Exp$
*
* @see <a href="http://en.wikipedia.org/wiki/Exchangeable_image_file_format">Wikipedia</a>
* @see <a href="http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html">Aware systems TIFF tag reference</a>
* @see <a href="http://partners.adobe.com/public/developer/tiff/index.html">Adobe TIFF developer resources</a>
*/
public final class PSDEXIF1Data extends PSDImageResource {
// protected byte[] mData;
protected Directory mDirectory;
PSDEXIF1Data(final short pId, final ImageInputStream pInput) throws IOException {
super(pId, pInput);
}
@Override
protected void readData(final ImageInputStream pInput) throws IOException {
// This is in essence an embedded TIFF file.
// TODO: Extract TIFF parsing to more general purpose package
// TODO: Instead, read the byte data, store for later parsing
MemoryCacheImageInputStream stream = new MemoryCacheImageInputStream(IIOUtil.createStreamAdapter(pInput, mSize));
byte[] bom = new byte[2];
stream.readFully(bom);
if (bom[0] == 'I' && bom[1] == 'I') {
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
}
else if (!(bom[0] == 'M' && bom[1] == 'M')) {
throw new IIOException(String.format("Invalid byte order marker '%s'", StringUtil.decode(bom, 0, bom.length, "ASCII")));
}
if (stream.readUnsignedShort() != 42) {
throw new IIOException("Wrong TIFF magic in EXIF data.");
}
long directoryOffset = stream.readUnsignedInt();
mDirectory = Directory.read(stream, directoryOffset);
}
@Override
public String toString() {
StringBuilder builder = toStringBuilder();
builder.append(", ").append(mDirectory);
builder.append("]");
return builder.toString();
}
// TIFF Image file directory (IFD)
private static class Directory {
List<Entry> mEntries = new ArrayList<Entry>();
private Directory() {}
public static Directory read(final ImageInputStream pInput, final long pOffset) throws IOException {
Directory directory = new Directory();
pInput.seek(pOffset);
int entryCount = pInput.readUnsignedShort();
for (int i = 0; i < entryCount; i++) {
directory.mEntries.add(Entry.read(pInput));
}
long nextOffset = pInput.readUnsignedInt();
if (nextOffset != 0) {
Directory next = Directory.read(pInput, nextOffset);
directory.mEntries.addAll(next.mEntries);
}
return directory;
}
@Override
public String toString() {
return String.format("Directory%s", mEntries);
}
}
// TIFF IFD Entry
private static class Entry {
private static final int EXIF_IFD = 0x8769;
private final static String[] TYPE_NAMES = {
"BYTE", "ASCII", "SHORT", "LONG", "RATIONAL",
"SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE",
};
private final static int[] TYPE_LENGTHS = {
1, 1, 2, 4, 8,
1, 1, 2, 4, 8, 4, 8,
};
private int mTag;
/*
1 = BYTE 8-bit unsigned integer.
2 = ASCII 8-bit byte that contains a 7-bit ASCII code; the last byte
must be NUL (binary zero).
3 = SHORT 16-bit (2-byte) unsigned integer.
4 = LONG 32-bit (4-byte) unsigned integer.
5 = RATIONAL Two LONGs: the first represents the numerator of a
fraction; the second, the denominator.
TIFF 6.0 and above:
6 = SBYTE An 8-bit signed (twos-complement) integer.
7 = UNDEFINED An 8-bit byte that may contain anything, depending on
the definition of the field.
8 = SSHORT A 16-bit (2-byte) signed (twos-complement) integer.
9 = SLONG A 32-bit (4-byte) signed (twos-complement) integer.
10 = SRATIONAL Two SLONGs: the first represents the numerator of a
fraction, the second the denominator.
11 = FLOAT Single precision (4-byte) IEEE format.
12 = DOUBLE Double precision (8-byte) IEEE format.
*/
private short mType;
private int mCount;
private long mValueOffset;
private Object mValue;
private Entry() {}
public static Entry read(final ImageInputStream pInput) throws IOException {
Entry entry = new Entry();
entry.mTag = pInput.readUnsignedShort();
entry.mType = pInput.readShort();
entry.mCount = pInput.readInt(); // Number of values
// TODO: Handle other sub-IFDs
if (entry.mTag == EXIF_IFD) {
long offset = pInput.readUnsignedInt();
pInput.mark();
try {
entry.mValue = Directory.read(pInput, offset);
}
finally {
pInput.reset();
}
}
else {
int valueLength = entry.getValueLength();
if (valueLength > 0 && valueLength <= 4) {
entry.readValueInLine(pInput);
pInput.skipBytes(4 - valueLength);
}
else {
entry.mValueOffset = pInput.readUnsignedInt(); // This is the *value* iff the value size is <= 4 bytes
entry.readValue(pInput);
}
}
return entry;
}
private void readValue(final ImageInputStream pInput) throws IOException {
long pos = pInput.getStreamPosition();
try {
pInput.seek(mValueOffset);
readValueInLine(pInput);
}
finally {
pInput.seek(pos);
}
}
private void readValueInLine(ImageInputStream pInput) throws IOException {
mValue = readValueDirect(pInput, mType, mCount);
}
private static Object readValueDirect(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
switch (pType) {
case 2:
// TODO: This might be UTF-8 or ISO-8859-1, even though against the spec
byte[] ascii = new byte[pCount];
pInput.readFully(ascii);
return StringUtil.decode(ascii, 0, ascii.length, "ASCII");
case 1:
if (pCount == 1) {
return pInput.readUnsignedByte();
}
case 6:
if (pCount == 1) {
return pInput.readByte();
}
case 7:
byte[] bytes = new byte[pCount];
pInput.readFully(bytes);
return bytes;
case 3:
if (pCount == 1) {
return pInput.readUnsignedShort();
}
case 8:
if (pCount == 1) {
return pInput.readShort();
}
short[] shorts = new short[pCount];
pInput.readFully(shorts, 0, shorts.length);
return shorts;
case 4:
if (pCount == 1) {
return pInput.readUnsignedInt();
}
case 9:
if (pCount == 1) {
return pInput.readInt();
}
int[] ints = new int[pCount];
pInput.readFully(ints, 0, ints.length);
return ints;
case 11:
if (pCount == 1) {
return pInput.readFloat();
}
float[] floats = new float[pCount];
pInput.readFully(floats, 0, floats.length);
return floats;
case 12:
if (pCount == 1) {
return pInput.readDouble();
}
double[] doubles = new double[pCount];
pInput.readFully(doubles, 0, doubles.length);
return doubles;
// TODO: Consider using a Rational class
case 5:
if (pCount == 1) {
return pInput.readUnsignedInt() / (double) pInput.readUnsignedInt();
}
double[] rationals = new double[pCount];
for (int i = 0; i < rationals.length; i++) {
rationals[i] = pInput.readUnsignedInt() / (double) pInput.readUnsignedInt();
}
return rationals;
case 10:
if (pCount == 1) {
return pInput.readInt() / (double) pInput.readInt();
}
double[] srationals = new double[pCount];
for (int i = 0; i < srationals.length; i++) {
srationals[i] = pInput.readInt() / (double) pInput.readInt();
}
return srationals;
default:
throw new IIOException(String.format("Unknown EXIF type '%s'", pType));
}
}
private int getValueLength() {
if (mType > 0 && mType <= TYPE_LENGTHS.length) {
return TYPE_LENGTHS[mType - 1] * mCount;
}
return -1;
}
private String getTypeName() {
if (mType > 0 && mType <= TYPE_NAMES.length) {
return TYPE_NAMES[mType - 1];
}
return "Unknown type";
}
// TODO: Tag names!
@Override
public String toString() {
return String.format("0x%04x: %s (%s, %d)", mTag, getValueAsString(), getTypeName(), mCount);
}
public String getValueAsString() {
if (mValue instanceof String) {
return String.format("\"%s\"", mValue);
}
if (mValue != null && mValue.getClass().isArray()) {
Class<?> type = mValue.getClass().getComponentType();
if (byte.class == type) {
return Arrays.toString((byte[]) mValue);
}
if (short.class == type) {
return Arrays.toString((short[]) mValue);
}
if (int.class == type) {
return Arrays.toString((int[]) mValue);
}
if (float.class == type) {
return Arrays.toString((float[]) mValue);
}
if (double.class == type) {
return Arrays.toString((double[]) mValue);
}
}
return String.valueOf(mValue);
}
}
}
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2008, 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 "TwelveMonkeys" 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 OWNER 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.plugins.psd;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
/**
* PSDGlobalLayerMask
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDGlobalLayerMask.java,v 1.0 May 8, 2008 5:33:48 PM haraldk Exp$
*/
class PSDGlobalLayerMask {
private int mColorSpace;
private int mColor1;
private int mColor2;
private int mColor3;
private int mColor4;
private int mOpacity;
private int mKind;
PSDGlobalLayerMask(ImageInputStream pInput) throws IOException {
mColorSpace = pInput.readUnsignedShort();
mColor1 = pInput.readUnsignedShort();
mColor2 = pInput.readUnsignedShort();
mColor3 = pInput.readUnsignedShort();
mColor4 = pInput.readUnsignedShort();
mOpacity = pInput.readUnsignedShort();
mKind = pInput.readUnsignedByte();
pInput.readByte(); // Pad
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(getClass().getSimpleName());
builder.append("[");
builder.append("color space: 0x").append(Integer.toHexString(mColorSpace));
builder.append(", colors: [0x").append(Integer.toHexString(mColor1));
builder.append(", 0x").append(Integer.toHexString(mColor2));
builder.append(", 0x").append(Integer.toHexString(mColor3));
builder.append(", 0x").append(Integer.toHexString(mColor4));
builder.append("], opacity: ").append(mOpacity);
builder.append(", kind: ").append(mKind);
builder.append("]");
return builder.toString();
}
}
@@ -0,0 +1,133 @@
/*
* Copyright (c) 2008, 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 "TwelveMonkeys" 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 OWNER 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.plugins.psd;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.IIOException;
import java.io.IOException;
/**
* PSDHeader
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDHeader.java,v 1.0 Apr 29, 2008 5:18:22 PM haraldk Exp$
*/
class PSDHeader {
// The header is 26 bytes in length and is structured as follows:
//
// typedef struct _PSD_HEADER
// {
// BYTE Signature[4]; /* File ID "8BPS" */
// WORD Version; /* Version number, always 1 */
// BYTE Reserved[6]; /* Reserved, must be zeroed */
// WORD Channels; /* Number of color channels (1-24) including alpha
// channels */
// LONG Rows; /* Height of image in pixels (1-30000) */
// LONG Columns; /* Width of image in pixels (1-30000) */
// WORD Depth; /* Number of bits per channel (1, 8, and 16) */
// WORD Mode; /* Color mode */
// } PSD_HEADER;
final short mChannels;
final int mWidth;
final int mHeight;
final short mBits;
final short mMode;
PSDHeader(ImageInputStream pInput) throws IOException {
int signature = pInput.readInt();
if (signature != PSD.SIGNATURE_8BPS) {
throw new IIOException("Not a PSD document, expected signature \"8BPS\": \"" + PSDUtil.intToStr(signature) + "\" (0x" + Integer.toHexString(signature) + ")");
}
int version = pInput.readUnsignedShort();
if (version != 1) {
if (version == 2) {
throw new IIOException("Large Document Format (PSB) not supported yet.");
}
throw new IIOException("Unknown PSD version, expected 1 or 2: 0x" + Integer.toHexString(version));
}
byte[] reserved = new byte[6];
pInput.readFully(reserved);
mChannels = pInput.readShort();
mHeight = pInput.readInt(); // Rows
mWidth = pInput.readInt(); // Coloumns
mBits = pInput.readShort();
mMode = pInput.readShort();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(getClass().getSimpleName());
builder.append("[Channels: ");
builder.append(mChannels);
builder.append(", width: ");
builder.append(mWidth);
builder.append(", height: ");
builder.append(mHeight);
builder.append(", depth: ");
builder.append(mBits);
builder.append(", mode: ");
builder.append(mMode);
switch (mMode) {
case PSD.COLOR_MODE_MONOCHROME:
builder.append(" (Monochrome)");
break;
case PSD.COLOR_MODE_GRAYSCALE:
builder.append(" (Grayscale)");
break;
case PSD.COLOR_MODE_INDEXED:
builder.append(" (Indexed)");
break;
case PSD.COLOR_MODE_RGB:
builder.append(" (RGB)");
break;
case PSD.COLOR_MODE_CMYK:
builder.append(" (CMYK)");
break;
case PSD.COLOR_MODE_MULTICHANNEL:
builder.append(" (Multi channel)");
break;
case PSD.COLOR_MODE_DUOTONE:
builder.append(" (Duotone)");
break;
case PSD.COLOR_MODE_LAB:
builder.append(" (Lab color)");
break;
default:
builder.append(" (Unkown mode)");
}
builder.append("]");
return builder.toString();
}
}
@@ -0,0 +1,575 @@
/*
* Copyright (c) 2008, 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 "TwelveMonkeys" 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 OWNER 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.plugins.psd;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.*;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
/**
* ImageReader for Adobe Photoshop Document format.
*
* @see <a href="http://www.fileformat.info/format/psd/egff.htm">Adobe Photoshop File Format Summary<a>
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDImageReader.java,v 1.0 Apr 29, 2008 4:45:52 PM haraldk Exp$
*/
// TODO: Implement AOI and subsampling
// TODO: Implement meta data reading
// TODO: Implement layer reading
// TODO: Allow reading separate (or some?) layers
// TODO: Consider Romain Guy's Java 2D implementation of PS filters for the blending modes in layers
// http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/
// See http://www.codeproject.com/KB/graphics/PSDParser.aspx
// See http://www.adobeforums.com/webx?14@@.3bc381dc/0
public class PSDImageReader extends ImageReaderBase {
private PSDHeader mHeader;
private PSDColorData mColorData;
private List<PSDImageResource> mImageResources;
private PSDGlobalLayerMask mGlobalLayerMask;
private List<PSDLayerInfo> mLayerInfo;
private ICC_ColorSpace mColorSpace;
protected PSDImageReader(final ImageReaderSpi pOriginatingProvider) {
super(pOriginatingProvider);
}
protected void resetMembers() {
mHeader = null;
mColorData = null;
mImageResources = null;
mColorSpace = null;
}
public int getWidth(int pIndex) throws IOException {
checkBounds(pIndex);
readHeader();
return mHeader.mWidth;
}
public int getHeight(int pIndex) throws IOException {
checkBounds(pIndex);
readHeader();
return mHeader.mHeight;
}
public Iterator<ImageTypeSpecifier> getImageTypes(int pIndex) throws IOException {
checkBounds(pIndex);
readHeader();
ColorSpace cs;
List<ImageTypeSpecifier> types = new ArrayList<ImageTypeSpecifier>();
switch (mHeader.mMode) {
case PSD.COLOR_MODE_INDEXED:
if (mHeader.mChannels == 1 && mHeader.mBits == 8) {
types.add(IndexedImageTypeSpecifier.createFromIndexColorModel(mColorData.getIndexColorModel()));
}
else {
throw new IIOException("Unsupported channel count/bit depth for Indexed Color PSD: " + mHeader.mChannels + " channels/" + mHeader.mBits + " bits");
}
break;
case PSD.COLOR_MODE_DUOTONE:
// NOTE: Duotone (whatever that is) should be treated as grayscale, so fall-through
case PSD.COLOR_MODE_GRAYSCALE:
if (mHeader.mChannels == 1 && mHeader.mBits == 8) {
types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY));
}
else if (mHeader.mChannels == 1 && mHeader.mBits == 16) {
types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_USHORT_GRAY));
}
else {
throw new IIOException("Unsupported channel count/bit depth for Gray Scale PSD: " + mHeader.mChannels + " channels/" + mHeader.mBits + " bits");
}
break;
case PSD.COLOR_MODE_RGB:
cs = getEmbeddedColorSpace();
if (cs == null) {
cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
}
if (mHeader.mChannels == 3 && mHeader.mBits == 8) {
// types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR));
types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {2, 1, 0}, DataBuffer.TYPE_BYTE, false, false));
}
else if (mHeader.mChannels >= 4 && mHeader.mBits == 8) {
// types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR));
types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, true, false));
}
else {
throw new IIOException("Unsupported channel count/bit depth for RGB PSD: " + mHeader.mChannels + " channels/" + mHeader.mBits + " bits");
}
break;
case PSD.COLOR_MODE_CMYK:
// TODO: We should convert these to their RGB equivalents while reading for the common-case,
// as Java2D is extremely slow displaying custom images.
// Converting to RGB is also correct behaviour, according to the docs.
// The code below is, however, correct for raw type.
cs = getEmbeddedColorSpace();
if (cs == null) {
cs = CMYKColorSpace.getInstance();
}
if (mHeader.mChannels == 4 && mHeader.mBits == 8) {
// types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR));
types.add(ImageTypeSpecifier.createInterleaved(cs, new int[]{3, 2, 1, 0}, DataBuffer.TYPE_BYTE, false, false));
}
else if (mHeader.mChannels == 5 && mHeader.mBits == 8) {
// types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR));
types.add(ImageTypeSpecifier.createInterleaved(cs, new int[]{4, 3, 2, 1, 0}, DataBuffer.TYPE_BYTE, true, false));
}
else {
throw new IIOException("Unsupported channel count/bit depth for CMYK PSD: " + mHeader.mChannels + " channels/" + mHeader.mBits + " bits");
}
break;
default:
throw new IIOException("Unsupported PSD MODE: " + mHeader.mMode);
}
return types.iterator();
}
private ColorSpace getEmbeddedColorSpace() throws IOException {
readImageResources(true);
// TODO: Skip this, requires storing some stream offsets
readLayerAndMaskInfo(false);
if (mColorSpace == null) {
ICC_Profile profile = null;
for (PSDImageResource resource : mImageResources) {
if (resource instanceof ICCProfile) {
profile = ((ICCProfile) resource).getProfile();
break;
}
}
mColorSpace = profile == null ? null : new ICC_ColorSpace(profile);
}
return mColorSpace;
}
// TODO: Implement param handling
public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException {
checkBounds(pIndex);
readHeader();
processImageStarted(pIndex);
readImageResources(false);
readLayerAndMaskInfo(false);
BufferedImage image = getDestination(pParam, getImageTypes(pIndex), mHeader.mWidth, mHeader.mHeight);
// TODO: Should do color convert op for CMYK -> RGB
ColorModel cm = image.getColorModel();
final boolean isCMYK = cm.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
final int numColorComponents = cm.getColorSpace().getNumComponents();
WritableRaster raster = image.getRaster();
if (!(raster.getDataBuffer() instanceof DataBufferByte)) {
throw new IIOException("Unsupported raster type: " + raster);
}
byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
// TODO: Maybe a banded raster would be easier than interleaved?
final int channels = raster.getNumBands();
// System.out.println("channels: " + channels);
// System.out.println("numColorComponents: " + numColorComponents);
// System.out.println("isCMYK: " + isCMYK);
short compression = mImageInput.readShort();
// TODO: Bitmap (depth = 1) and 16 bit (depth = 16) must be read differently, obviously...
// This code works fine for images with channel depth = 8
switch (compression) {
case PSD.COMPRESSION_NONE:
// TODO: This entire reading block is duplicated and should be replaced with the one for RLE!
// System.out.println("Uncompressed");
for (int c = 0; c < mHeader.mChannels; c++) {
for (int y = 0; y < mHeader.mHeight; y++) {
for (int x = 0; x < mHeader.mWidth; x++) {
int offset = (x + y * mHeader.mWidth) * channels;
byte value = mImageInput.readByte();
// CMYK values are stored inverted, but alpha is not
if (isCMYK && c < numColorComponents) {
value = (byte) (255 - value & 0xff);
}
// System.out.println("b: " + Integer.toHexString(b & 0xff));
data[offset + (channels - 1 - c)] = value;
}
if (abortRequested()) {
break;
}
processImageProgress((c * y * 100) / mHeader.mChannels * mHeader.mHeight);
}
if (abortRequested()) {
break;
}
}
break;
case PSD.COMPRESSION_RLE:
// System.out.println("PackBits compressed");
// NOTE: Offsets will allow us to easily skip rows before AOI
int[] offsets = new int[mHeader.mChannels * mHeader.mHeight];
for (int i = 0; i < offsets.length; i++) {
offsets[i] = mImageInput.readUnsignedShort();
}
int x = 0, y = 0, c = 0;
try {
for (c = 0; c < channels; c++) {
for (y = 0; y < mHeader.mHeight; y++) {
int length = offsets[c * mHeader.mHeight + y];
// System.out.println("channel: " + c + " line: " + y + " length: " + length);
DataInputStream input = PSDUtil.createPackBitsStream(mImageInput, length);
for (x = 0; x < mHeader.mWidth; x++) {
int offset = (x + y * mHeader.mWidth) * channels;
byte value = input.readByte();
// if (c < numColorComponents) {
// continue;
// }
// CMYK values are stored inverted, but alpha is not
if (isCMYK && c < numColorComponents) {
value = (byte) (255 - value & 0xff);
}
// System.out.println("b: " + Integer.toHexString(b & 0xff));
data[offset + (channels - 1 - c)] = value;
}
input.close();
if (abortRequested()) {
break;
}
processImageProgress((c * y * 100) / mHeader.mChannels * mHeader.mHeight);
}
if (abortRequested()) {
break;
}
}
}
catch (IOException e) {
System.err.println("c: " + c);
System.err.println("y: " + y);
System.err.println("x: " + x);
throw e;
}
catch (IndexOutOfBoundsException e) {
e.printStackTrace();
System.out.println("data.length: " + data.length);
System.err.println("c: " + c);
System.err.println("y: " + y);
System.err.println("x: " + x);
throw e;
}
break;
case PSD.COMPRESSION_ZIP:
// TODO: Could probably use the ZIPDecoder (DeflateDecoder) here..
case PSD.COMPRESSION_ZIP_PREDICTON:
// TODO: Need to find out if the normal java.util.zip can handle this...
// Could be same as PNG prediction? Read up...
throw new IIOException("ZIP compression not supported yet");
default:
throw new IIOException("Unknown compression type: " + compression);
}
// Compose out the background of the semi-transparent pixels, as PS somehow has the background composed in
decomposeAlpha(image);
if (abortRequested()) {
processReadAborted();
}
else {
processImageComplete();
}
return image;
}
private void decomposeAlpha(final BufferedImage pImage) throws IOException {
ColorModel cm = pImage.getColorModel();
// TODO: What about CMYK + alpha?
if (cm.hasAlpha() && cm.getColorSpace().getType() == ColorSpace.TYPE_RGB) {
WritableRaster raster = pImage.getRaster();
// TODO: Probably faster to do this inline..
// TODO: This is not so good, as it might break acceleration...
byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
final int w = pImage.getWidth();
final int channels = raster.getNumBands();
for (int y = 0; y < pImage.getHeight(); y++) {
for (int x = 0; x < w; x++) {
int offset = (x + y * w) * channels;
// TODO: Is the document background always white!?
// ABGR format
int alpha = data[offset] & 0xff;
if (alpha != 0) {
double normalizedAlpha = alpha / 255.0;
for (int i = 1; i < channels; i++) {
data[offset + i] = decompose(data[offset + i] & 0xff, normalizedAlpha);
}
}
else {
for (int i = 1; i < channels; i++) {
data[offset + i] = 0;
}
}
}
}
}
// System.out.println("PSDImageReader.coerceData: " + cm.getClass());
// System.out.println("other.equals(cm): " + (other == cm));
}
private static byte decompose(final int pColor, final double pAlpha) {
// Adapted from Computer Graphics: Principles and Practice (Foley et al.), p. 837
double color = pColor / 255.0;
return (byte) ((color / pAlpha - ((1 - pAlpha) / pAlpha)) * 255);
}
private void readHeader() throws IOException {
assertInput();
if (mHeader == null) {
mHeader = new PSDHeader(mImageInput);
/*
Contains the required data to define the color mode.
For indexed color images, the count will be equal to 768, and the mode data
will contain the color table for the image, in non-interleaved order.
For duotone images, the mode data will contain the duotone specification,
the format of which is not documented. Non-Photoshop readers can treat
the duotone image as a grayscale image, and keep the duotone specification
around as a black box for use when saving the file.
*/
if (mHeader.mMode == PSD.COLOR_MODE_INDEXED) {
mColorData = new PSDColorData(mImageInput);
}
else {
// Skip color mode data for other modes
long length = mImageInput.readUnsignedInt();
mImageInput.skipBytes(length);
}
// Don't need the header again
mImageInput.flushBefore(mImageInput.getStreamPosition());
}
}
private void readImageResources(boolean pParseData) throws IOException {
// TODO: Avoid unnecessary stream repositioning
long pos = mImageInput.getFlushedPosition();
mImageInput.seek(pos);
long length = mImageInput.readUnsignedInt();
if (pParseData && length > 0) {
if (mImageResources == null) {
mImageResources = new ArrayList<PSDImageResource>();
long expectedEnd = mImageInput.getStreamPosition() + length;
while (mImageInput.getStreamPosition() < expectedEnd) {
PSDImageResource resource = PSDImageResource.read(mImageInput);
mImageResources.add(resource);
}
if (mImageInput.getStreamPosition() != expectedEnd) {
throw new IIOException("Corrupt PSD document");
}
}
}
mImageInput.seek(pos + length + 4);
}
private void readLayerAndMaskInfo(boolean pParseData) throws IOException {
// TODO: Make sure we are positioned correctly
long length = mImageInput.readUnsignedInt();
if (pParseData && length > 0) {
long pos = mImageInput.getStreamPosition();
long layerInfoLength = mImageInput.readUnsignedInt();
/*
"Layer count. If it is a negative number, its absolute value is the number of
layers and the first alpha channel contains the transparency data for the
merged result."
*/
// TODO: Figure out what the last part of that sentence means in practice...
int layers = mImageInput.readShort();
// System.out.println("layers: " + layers);
PSDLayerInfo[] layerInfo = new PSDLayerInfo[Math.abs(layers)];
for (int i = 0; i < layerInfo.length; i++) {
layerInfo[i] = new PSDLayerInfo(mImageInput);
// System.out.println("layerInfo[" + i + "]: " + layerInfo[i]);
}
mLayerInfo = Arrays.asList(layerInfo);
for (PSDLayerInfo info : layerInfo) {
for (PSDChannelInfo channelInfo : info.mChannelInfo) {
int compression = mImageInput.readUnsignedShort();
// 0: None, 1: PackBits RLE, 2: Zip, 3: Zip w/prediction
switch (compression) {
case PSD.COMPRESSION_NONE:
// System.out.println("Compression: None");
break;
case PSD.COMPRESSION_RLE:
// System.out.println("Compression: PackBits RLE");
break;
case PSD.COMPRESSION_ZIP:
// System.out.println("Compression: ZIP");
break;
case PSD.COMPRESSION_ZIP_PREDICTON:
// System.out.println("Compression: ZIP with prediction");
break;
default:
// TODO: Do we care, as we can just skip the data?
// We could issue a warning to the warning listener
throw new IIOException(String.format(
"Unknown PSD compression: %d. Expected 0 (none), 1 (RLE), 2 (ZIP) or 3 (ZIP w/prediction).",
compression
));
}
// TODO: If RLE, the the image data starts with the byte counts
// for all the scan lines in the channel (LayerBottom*LayerTop), with
// each count stored as a two*byte value.
// if (compression == 1) {
// mImageInput.skipBytes(channelInfo.mLength);
// }
// TODO: Read channel image data (same format as composite image channel data)
mImageInput.skipBytes(channelInfo.mLength - 2);
// if (channelInfo.mLength % 2 != 0) {
// mImageInput.readByte();
// }
}
}
// TODO: We seem to have some alignment issues here...
// I'm always reading two bytes off..
long read = mImageInput.getStreamPosition() - pos;
// System.out.println("layerInfoLength: " + layerInfoLength);
// System.out.println("layer info read: " + (read - 4)); // - 4 for the layerInfoLength field itself
long diff = layerInfoLength - (read - 4);
// System.out.println("diff: " + diff);
mImageInput.skipBytes(diff);
// TODO: Global LayerMaskInfo (18 bytes or more..?)
// 4 (length), 2 (colorSpace), 8 (4 * 2 byte color components), 2 (opacity %), 1 (kind), variable (pad)
long layerMaskInfoLength = mImageInput.readUnsignedInt();
// System.out.println("GlobalLayerMaskInfo length: " + layerMaskInfoLength);
if (layerMaskInfoLength > 0) {
mGlobalLayerMask = new PSDGlobalLayerMask(mImageInput);
}
read = mImageInput.getStreamPosition() - pos;
long toSkip = length - read;
// System.out.println("toSkip: " + toSkip);
mImageInput.skipBytes(toSkip);
}
else {
mImageInput.skipBytes(length);
}
}
public static void main(String[] pArgs) throws IOException {
PSDImageReader imageReader = new PSDImageReader(null);
File file = new File(pArgs[0]);
ImageInputStream stream = ImageIO.createImageInputStream(file);
imageReader.setInput(stream);
imageReader.readHeader();
System.out.println("imageReader.mHeader: " + imageReader.mHeader);
imageReader.readImageResources(true);
System.out.println("imageReader.mImageResources: " + imageReader.mImageResources);
imageReader.readLayerAndMaskInfo(true);
System.out.println("imageReader.mLayerInfo: " + imageReader.mLayerInfo);
System.out.println("imageReader.mGlobalLayerMask: " + imageReader.mGlobalLayerMask);
long start = System.currentTimeMillis();
ImageReadParam param = new ImageReadParam();
// param.setSourceRegion(new Rectangle(100, 100, 300, 200));
BufferedImage image = imageReader.read(0, param);
System.out.println("time: " + (System.currentTimeMillis() - start));
System.out.println("image: " + image);
if (image.getColorModel().getColorSpace().getType() == ColorSpace.TYPE_CMYK) {
try {
ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null);
image = op.filter(image, new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_4BYTE_ABGR_PRE));
}
catch (Exception e) {
e.printStackTrace();
image = ImageUtil.accelerate(image);
}
System.out.println("time: " + (System.currentTimeMillis() - start));
System.out.println("image: " + image);
}
showIt(image, file.getName());
}
}
@@ -0,0 +1,92 @@
/*
* Copyright (c) 2008, 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 "TwelveMonkeys" 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 OWNER 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.plugins.psd;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.ImageReader;
import java.io.IOException;
import java.util.Locale;
/**
* PSDImageReaderSpi
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDImageReaderSpi.java,v 1.0 Apr 29, 2008 4:49:03 PM haraldk Exp$
*/
public class PSDImageReaderSpi extends ImageReaderSpi {
/**
* Creates an PSDImageReaderSpi
*/
public PSDImageReaderSpi() {
super(
"TwelveMonkeys",
"2.0",
new String[]{"psd", "PSD"},
new String[]{"psd"},
new String[]{
"application/vnd.adobe.photoshop", // This one seems official, used in XMP
"image/x-psd", "application/x-photoshop", "image/x-photoshop"
},
"com.twelvemkonkeys.imageio.plugins.psd.PSDImageReader",
STANDARD_INPUT_TYPE,
// new String[]{"com.twelvemkonkeys.imageio.plugins.psd.PSDImageWriterSpi"},
null,
true, null, null, null, null,
true, null, null, null, null
);
}
public boolean canDecodeInput(Object pSource) throws IOException {
if (!(pSource instanceof ImageInputStream)) {
return false;
}
ImageInputStream stream = (ImageInputStream) pSource;
stream.mark();
try {
return stream.readInt() == PSD.SIGNATURE_8BPS;
// TODO: Test more of the header, see PSDImageReader#readHeader
}
finally {
stream.reset();
}
}
public ImageReader createReaderInstance(Object pExtension) throws IOException {
return new PSDImageReader(this);
}
public String getDescription(Locale pLocale) {
return "Adobe Photoshop Document (PSD) image reader";
}
}
@@ -0,0 +1,168 @@
/*
* Copyright (c) 2008, 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 "TwelveMonkeys" 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 OWNER 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.plugins.psd;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.IIOException;
import java.io.IOException;
import java.lang.reflect.Field;
/**
* PSDImageResource
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDImageResource.java,v 1.0 Apr 29, 2008 5:49:06 PM haraldk Exp$
*/
class PSDImageResource {
final short mId;
final String mName;
final long mSize;
PSDImageResource(final short pId, final ImageInputStream pInput) throws IOException {
mId = pId;
mName = PSDUtil.readPascalString(pInput);
mSize = pInput.readUnsignedInt();
readData(pInput);
// Data is even-padded
if (mSize % 2 != 0) {
pInput.read();
}
}
/**
* This default implementation simply skips the data.
*
* @param pInput the input
* @throws IOException if an I/O exception occurs
*/
protected void readData(final ImageInputStream pInput) throws IOException {
// TODO: This design is ugly, as subclasses readData is invoked BEFORE their respective constructor...
pInput.skipBytes(mSize);
}
@Override
public String toString() {
StringBuilder builder = toStringBuilder();
builder.append(", data length: ");
builder.append(mSize);
builder.append("]");
return builder.toString();
}
protected StringBuilder toStringBuilder() {
StringBuilder builder = new StringBuilder(getClass().getSimpleName());
builder.append(resourceTypeForId(mId));
builder.append("[ID: 0x");
builder.append(Integer.toHexString(mId));
if (mName != null && mName.trim().length() != 0) {
builder.append(", name: \"");
builder.append(mName);
builder.append("\"");
}
return builder;
}
static String resourceTypeForId(final short pId) {
switch (pId) {
case PSD.RES_RESOLUTION_INFO:
case PSD.RES_ALPHA_CHANNEL_INFO:
case PSD.RES_DISPLAY_INFO:
case PSD.RES_PRINT_FLAGS:
case PSD.RES_THUMBNAIL_PS4:
case PSD.RES_THUMBNAIL:
case PSD.RES_ICC_PROFILE:
case PSD.RES_EXIF_DATA_1:
// case PSD.RES_EXIF_DATA_3:
case PSD.RES_XMP_DATA:
case PSD.RES_PRINT_FLAGS_INFORMATION:
return "";
default:
try {
for (Field field : PSD.class.getDeclaredFields()) {
if (field.getName().startsWith("RES_") && field.getInt(null) == pId) {
return "(" + field.getName().substring(4) + ")";
}
}
}
catch (IllegalAccessException ignore) {
}
return "(unknown resource)";
}
}
public static PSDImageResource read(final ImageInputStream pInput) throws IOException {
int type = pInput.readInt();
if (type != PSD.RESOURCE_TYPE) {
throw new IIOException("Wrong image resource type, expected 8BIM: " + PSDUtil.intToStr(type));
}
// TODO: Process more of the resource stuff, most important are IPTC, EXIF and XMP data,
// version info, and thumbnail for thumbnail-support.
short id = pInput.readShort();
switch (id) {
case PSD.RES_RESOLUTION_INFO:
return new PSDResolutionInfo(id, pInput);
case PSD.RES_ALPHA_CHANNEL_INFO:
return new PSDAlphaChannelInfo(id, pInput);
case PSD.RES_DISPLAY_INFO:
return new PSDDisplayInfo(id, pInput);
case PSD.RES_PRINT_FLAGS:
return new PSDPrintFlags(id, pInput);
case PSD.RES_THUMBNAIL_PS4:
case PSD.RES_THUMBNAIL:
return new PSDThumbnail(id, pInput);
case PSD.RES_ICC_PROFILE:
return new ICCProfile(id, pInput);
case PSD.RES_EXIF_DATA_1:
return new PSDEXIF1Data(id, pInput);
case PSD.RES_XMP_DATA:
return new PSDXMPData(id, pInput);
case PSD.RES_PRINT_FLAGS_INFORMATION:
return new PSDPrintFlagsInformation(id, pInput);
default:
if (id >= 0x07d0 && id <= 0x0bb6) {
// TODO: Parse saved path information
return new PSDImageResource(id, pInput);
}
else {
return new PSDImageResource(id, pInput);
}
}
}
}
@@ -0,0 +1,117 @@
/*
* Copyright (c) 2008, 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 "TwelveMonkeys" 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 OWNER 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.plugins.psd;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.IIOException;
import java.io.IOException;
/**
* PSDLayerBlendMode
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDLayerBlendMode.java,v 1.0 May 8, 2008 4:34:35 PM haraldk Exp$
*/
class PSDLayerBlendMode {
final int mBlendMode;
final int mOpacity; // 0-255
final int mClipping; // 0: base, 1: non-base
final int mFlags;
public PSDLayerBlendMode(ImageInputStream pInput) throws IOException {
int blendModeSig = pInput.readInt();
if (blendModeSig != PSD.RESOURCE_TYPE) { // TODO: Is this really just a resource?
throw new IIOException("Illegal PSD Blend Mode signature, expected 8BIM: " + PSDUtil.intToStr(blendModeSig));
}
mBlendMode = pInput.readInt();
mOpacity = pInput.readUnsignedByte();
mClipping = pInput.readUnsignedByte();
mFlags = pInput.readUnsignedByte();
pInput.readByte(); // Pad
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(getClass().getSimpleName());
builder.append("[");
builder.append("mode: \"").append(PSDUtil.intToStr(mBlendMode));
builder.append("\", opacity: ").append(mOpacity);
builder.append(", clipping: ").append(mClipping);
builder.append(", flags: ").append(byteToBinary(mFlags));
// TODO: Maybe the flag bits have oposite order?
builder.append(" (");
if ((mFlags & 0x01) != 0) {
builder.append("Transp. protected ");
}
else {
builder.append("Transp. open");
}
if ((mFlags & 0x02) != 0) {
builder.append(", Visible");
}
else {
builder.append(", Hidden");
}
if ((mFlags & 0x04) != 0) {
builder.append(", Obsolete bit");
}
if ((mFlags & 0x08) != 0) {
builder.append(", Photoshop 5 data");
}
if ((mFlags & 0x10) != 0) {
builder.append(", Pixel data irrelevant");
}
if ((mFlags & 0x20) != 0) {
builder.append(", Unknown bit 5");
}
if ((mFlags & 0x40) != 0) {
builder.append(", Unknown bit 6");
}
if ((mFlags & 0x80) != 0) {
builder.append(", Unknown bit 7");
}
builder.append(")");
builder.append("]");
return builder.toString();
}
private static String byteToBinary(final int pFlags) {
String flagStr = Integer.toBinaryString(pFlags);
flagStr = "00000000".substring(flagStr.length()) + flagStr;
return flagStr;
}
}
@@ -0,0 +1,133 @@
/*
* Copyright (c) 2008, 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 "TwelveMonkeys" 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 OWNER 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.plugins.psd;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.IIOException;
import java.io.IOException;
import java.util.Arrays;
/**
* PSDLayerInfo
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDLayerInfo.java,v 1.0 Apr 29, 2008 6:01:12 PM haraldk Exp$
*/
class PSDLayerInfo {
private int mTop;
private int mLeft;
private int mBottom;
private int mRight;
PSDChannelInfo[] mChannelInfo;
private PSDLayerBlendMode mBlendMode;
private PSDLayerMaskData mLayerMaskData;
private PSDChannelSourceDestinationRange[] mRanges;
private String mLayerName;
PSDLayerInfo(ImageInputStream pInput) throws IOException {
mTop = pInput.readInt();
mLeft = pInput.readInt();
mBottom = pInput.readInt();
mRight = pInput.readInt();
int channels = pInput.readUnsignedShort();
mChannelInfo = new PSDChannelInfo[channels];
for (int i = 0; i < channels; i++) {
short channelId = pInput.readShort();
long length = pInput.readUnsignedInt();
mChannelInfo[i] = new PSDChannelInfo(channelId, length);
}
mBlendMode = new PSDLayerBlendMode(pInput);
// Lenght of layer mask data
long extraDataSize = pInput.readUnsignedInt();
// TODO: Allow skipping the rest here?
// pInput.skipBytes(extraDataSize);
// Layer mask/adjustment layer data
int layerMaskDataSize = pInput.readInt(); // May be 0, 20 or 36 bytes...
if (layerMaskDataSize != 0) {
mLayerMaskData = new PSDLayerMaskData(pInput, layerMaskDataSize);
}
int layerBlendingDataSize = pInput.readInt();
if (layerBlendingDataSize % 8 != 0) {
throw new IIOException("Illegal PSD Layer Blending Data size: " + layerBlendingDataSize + ", expected multiple of 8");
}
mRanges = new PSDChannelSourceDestinationRange[layerBlendingDataSize / 8];
for (int i = 0; i < mRanges.length; i++) {
mRanges[i] = new PSDChannelSourceDestinationRange(pInput, (i == 0 ? "Gray" : "Channel " + (i - 1)));
}
mLayerName = PSDUtil.readPascalString(pInput);
int layerNameSize = mLayerName.length() + 1;
// readPascalString has already read pad byte for word alignment
if (layerNameSize % 2 != 0) {
layerNameSize++;
}
// Skip two more pad bytes if needed
if (layerNameSize % 4 != 0) {
pInput.skipBytes(2);
layerNameSize += 2;
}
// TODO: There's some data skipped here...
// Adjustment layer info etc...
pInput.skipBytes(extraDataSize - layerMaskDataSize - 4 - layerBlendingDataSize - 4 - layerNameSize);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(getClass().getSimpleName());
builder.append("[");
builder.append("top: ").append(mTop);
builder.append(", left: ").append(mLeft);
builder.append(", bottom: ").append(mBottom);
builder.append(", right: ").append(mRight);
builder.append(", channels: ").append(Arrays.toString(mChannelInfo));
builder.append(", blend mode: ").append(mBlendMode);
if (mLayerMaskData != null) {
builder.append(", layer mask data: ").append(mLayerMaskData);
}
builder.append(", ranges: ").append(Arrays.toString(mRanges));
builder.append(", layer name: \"").append(mLayerName).append("\"");
builder.append("]");
return builder.toString();
}
}
@@ -0,0 +1,136 @@
/*
* Copyright (c) 2008, 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 "TwelveMonkeys" 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 OWNER 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.plugins.psd;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
/**
* PSDLayerMaskData
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDLayerMaskData.java,v 1.0 May 6, 2008 5:15:05 PM haraldk Exp$
*/
class PSDLayerMaskData {
private int mTop;
private int mLeft;
private int mBottom;
private int mRight;
private int mDefaultColor;
private int mFlags;
private boolean mLarge;
private int mRealFlags;
private int mRealUserBackground;
private int mRealTop;
private int mRealLeft;
private int mRealBottom;
private int mRealRight;
PSDLayerMaskData(ImageInputStream pInput, int pSize) throws IOException {
if (pSize != 20 && pSize != 36) {
throw new IIOException("Illegal PSD Layer Mask data size: " + pSize + " (expeced 20 or 36)");
}
mTop = pInput.readInt();
mLeft = pInput.readInt();
mBottom = pInput.readInt();
mRight = pInput.readInt();
mDefaultColor = pInput.readUnsignedByte();
mFlags = pInput.readUnsignedByte();
if (pSize == 20) {
pInput.readShort(); // Pad
}
else {
// TODO: What to make out of this?
mLarge = true;
mRealFlags = pInput.readUnsignedByte();
mRealUserBackground = pInput.readUnsignedByte();
mRealTop = pInput.readInt();
mRealLeft = pInput.readInt();
mRealBottom = pInput.readInt();
mRealRight = pInput.readInt();
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(getClass().getSimpleName());
builder.append("[");
builder.append("top: ").append(mTop);
builder.append(", left: ").append(mLeft);
builder.append(", bottom: ").append(mBottom);
builder.append(", right: ").append(mRight);
builder.append(", default color: ").append(mDefaultColor);
builder.append(", flags: ").append(Integer.toBinaryString(mFlags));
// TODO: Maybe the flag bits have oposite order?
builder.append(" (");
if ((mFlags & 0x01) != 0) {
builder.append("Pos. rel. to layer");
}
else {
builder.append("Pos. abs.");
}
if ((mFlags & 0x02) != 0) {
builder.append(", Mask disabled");
}
else {
builder.append(", Mask enabled");
}
if ((mFlags & 0x04) != 0) {
builder.append(", Invert mask");
}
if ((mFlags & 0x08) != 0) {
builder.append(", Unknown bit 3");
}
if ((mFlags & 0x10) != 0) {
builder.append(", Unknown bit 4");
}
if ((mFlags & 0x20) != 0) {
builder.append(", Unknown bit 5");
}
if ((mFlags & 0x40) != 0) {
builder.append(", Unknown bit 6");
}
if ((mFlags & 0x80) != 0) {
builder.append(", Unknown bit 7");
}
builder.append(")");
builder.append("]");
return builder.toString();
}
}
@@ -0,0 +1,58 @@
package com.twelvemonkeys.imageio.plugins.psd;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
/**
* PSDPrintFlagsInfo
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDPrintFlagsInfo.java,v 1.0 Jul 28, 2009 5:16:27 PM haraldk Exp$
*/
final class PSDPrintFlags extends PSDImageResource {
private boolean mLabels;
private boolean mCropMasks;
private boolean mColorBars;
private boolean mRegistrationMarks;
private boolean mNegative;
private boolean mFlip;
private boolean mInterpolate;
private boolean mCaption;
PSDPrintFlags(final short pId, final ImageInputStream pInput) throws IOException {
super(pId, pInput);
}
@Override
protected void readData(final ImageInputStream pInput) throws IOException {
mLabels = pInput.readUnsignedByte() != 0;
mCropMasks = pInput.readUnsignedByte() != 0;
mColorBars = pInput.readUnsignedByte() != 0;
mRegistrationMarks = pInput.readUnsignedByte() != 0;
mNegative = pInput.readUnsignedByte() != 0;
mFlip = pInput.readUnsignedByte() != 0;
mInterpolate = pInput.readUnsignedByte() != 0;
mCaption = pInput.readUnsignedByte() != 0;
pInput.readUnsignedByte(); // Pad
}
@Override
public String toString() {
StringBuilder builder = toStringBuilder();
builder.append(", labels: ").append(mLabels);
builder.append(", crop masks: ").append(mCropMasks);
builder.append(", color bars: ").append(mColorBars);
builder.append(", registration marks: ").append(mRegistrationMarks);
builder.append(", negative: ").append(mNegative);
builder.append(", flip: ").append(mFlip);
builder.append(", interpolate: ").append(mInterpolate);
builder.append(", caption: ").append(mCaption);
builder.append("]");
return builder.toString();
}
}
@@ -0,0 +1,47 @@
package com.twelvemonkeys.imageio.plugins.psd;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
/**
* PSDPrintFlagsInfo
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDPrintFlagsInfo.java,v 1.0 Jul 28, 2009 5:16:27 PM haraldk Exp$
*/
final class PSDPrintFlagsInformation extends PSDImageResource {
private int mVersion;
private boolean mCropMasks;
private int mField;
private long mBleedWidth;
private int mBleedScale;
PSDPrintFlagsInformation(final short pId, final ImageInputStream pInput) throws IOException {
super(pId, pInput);
}
@Override
protected void readData(final ImageInputStream pInput) throws IOException {
mVersion = pInput.readUnsignedShort();
mCropMasks = pInput.readUnsignedByte() != 0;
mField = pInput.readUnsignedByte();
mBleedWidth = pInput.readUnsignedInt();
mBleedScale = pInput.readUnsignedShort();
}
@Override
public String toString() {
StringBuilder builder = toStringBuilder();
builder.append(", version: ").append(mVersion);
builder.append(", crop masks: ").append(mCropMasks);
builder.append(", field: ").append(mField);
builder.append(", bleed width: ").append(mBleedWidth);
builder.append(", bleed scale: ").append(mBleedScale);
builder.append("]");
return builder.toString();
}
}
@@ -0,0 +1,555 @@
package com.twelvemonkeys.imageio.plugins.psd;
import java.awt.*;
import java.awt.image.*;
import java.net.*;
import java.io.*;
/**
* Class PSDReader - Decodes a PhotoShop (.psd) file into one or more frames.
* Supports uncompressed or RLE-compressed RGB files only. Each layer may be
* retrieved as a full frame BufferedImage, or as a smaller image with an
* offset if the layer does not occupy the full frame size. Transparency
* in the original psd file is preserved in the returned BufferedImage's.
* Does not support additional features in PS versions higher than 3.0.
* Example:
* <br><pre>
* PSDReader r = new PSDReader();
* r.readData("sample.psd");
* int n = r.getFrameCount();
* for (int i = 0; i < n; i++) {
* BufferedImage image = r.getLayer(i);
* Point offset = r.getLayerOffset(i);
* // do something with image
* }
* </pre>
* No copyright asserted on the source code of this class. May be used for
* any purpose. Please forward any corrections to kweiner@fmsware.com.
*
* @author Kevin Weiner, FM Software.
* @version 1.1 January 2004 [bug fix; add RLE support]
*
*/
// SEE: http://www.fileformat.info/format/psd/egff.htm#ADOBEPHO-DMYID.4
class PSDReader {
/**
* File readData status: No errors.
*/
public static final int STATUS_OK = 0;
/**
* File readData status: Error decoding file (may be partially decoded)
*/
public static final int STATUS_FORMAT_ERROR = 1;
/**
* File readData status: Unable to open source.
*/
public static final int STATUS_OPEN_ERROR = 2;
/**
* File readData status: Unsupported format
*/
public static final int STATUS_UNSUPPORTED = 3;
public static int ImageType = BufferedImage.TYPE_INT_ARGB;
protected BufferedInputStream input;
protected int frameCount;
protected BufferedImage[] frames;
protected int status = 0;
protected int nChan;
protected int width;
protected int height;
protected int nLayers;
protected int miscLen;
protected boolean hasLayers;
protected LayerInfo[] layers;
protected short[] lineLengths;
protected int lineIndex;
protected boolean rleEncoded;
protected class LayerInfo {
int x, y, w, h;
int nChan;
int[] chanID;
int alpha;
}
/**
* Gets the number of layers readData from file.
* @return frame count
*/
public int getFrameCount() {
return frameCount;
}
protected void setInput(InputStream stream) {
// open input stream
init();
if (stream == null) {
status = STATUS_OPEN_ERROR;
} else {
if (stream instanceof BufferedInputStream)
input = (BufferedInputStream) stream;
else
input = new BufferedInputStream(stream);
}
}
protected void setInput(String name) {
// open input file
init();
try {
name = name.trim();
if (name.startsWith("file:")) {
name = name.substring(5);
while (name.startsWith("/"))
name = name.substring(1);
}
if (name.indexOf("://") > 0) {
URL url = new URL(name);
input = new BufferedInputStream(url.openStream());
} else {
input = new BufferedInputStream(new FileInputStream(name));
}
} catch (IOException e) {
status = STATUS_OPEN_ERROR;
}
}
/**
* Gets display duration for specified frame. Always returns 0.
*
*/
public int getDelay(int forFrame) {
return 0;
}
/**
* Gets the image contents of frame n. Note that this expands the image
* to the full frame size (if the layer was smaller) and any subsequent
* use of getLayer() will return the full image.
*
* @return BufferedImage representation of frame, or null if n is invalid.
*/
public BufferedImage getFrame(int n) {
BufferedImage im = null;
if ((n >= 0) && (n < nLayers)) {
im = frames[n];
LayerInfo info = layers[n];
if ((info.w != width) || (info.h != height)) {
BufferedImage temp =
new BufferedImage(width, height, ImageType);
Graphics2D gc = temp.createGraphics();
gc.drawImage(im, info.x, info.y, null);
gc.dispose();
im = temp;
frames[n] = im;
}
}
return im;
}
/**
* Gets maximum image size. Individual layers may be smaller.
*
* @return maximum image dimensions
*/
public Dimension getFrameSize() {
return new Dimension(width, height);
}
/**
* Gets the first (or only) image readData.
*
* @return BufferedImage containing first frame, or null if none.
*/
public BufferedImage getImage() {
return getFrame(0);
}
/**
* Gets the image contents of layer n. May be smaller than full frame
* size - use getFrameOffset() to obtain position of subimage within
* main image area.
*
* @return BufferedImage representation of layer, or null if n is invalid.
*/
public BufferedImage getLayer(int n) {
BufferedImage im = null;
if ((n >= 0) && (n < nLayers)) {
im = frames[n];
}
return im;
}
/**
* Gets the subimage offset of layer n if it is smaller than the
* full frame size.
*
* @return Point indicating offset from upper left corner of frame.
*/
public Point getLayerOffset(int n) {
Point p = null;
if ((n >= 0) && (n < nLayers)) {
int x = layers[n].x;
int y = layers[n].y;
p = new Point(x, y);
}
if (p == null) {
p = new Point(0, 0);
}
return p;
}
/**
* Reads PhotoShop layers from stream.
*
* @param InputStream in PhotoShop format.
* @return readData status code (0 = no errors)
*/
public int read(InputStream stream) {
setInput(stream);
process();
return status;
}
/**
* Reads PhotoShop file from specified source (file or URL string)
*
* @param name String containing source
* @return readData status code (0 = no errors)
*/
public int read(String name) {
setInput(name);
process();
return status;
}
/**
* Closes input stream and discards contents of all frames.
*
*/
public void reset() {
init();
}
protected void close() {
if (input != null) {
try {
input.close();
} catch (Exception e) {}
input = null;
}
}
protected boolean err() {
return status != STATUS_OK;
}
protected byte[] fillBytes(int size, int value) {
// create byte array filled with given value
byte[] b = new byte[size];
if (value != 0) {
byte v = (byte) value;
for (int i = 0; i < size; i++) {
b[i] = v;
}
}
return b;
}
protected void init() {
close();
frameCount = 0;
frames = null;
layers = null;
hasLayers = true;
status = STATUS_OK;
}
protected void makeDummyLayer() {
// creat dummy layer for non-layered image
rleEncoded = readShort() == 1;
hasLayers = false;
nLayers = 1;
layers = new LayerInfo[1];
LayerInfo layer = new LayerInfo();
layers[0] = layer;
layer.h = height;
layer.w = width;
int nc = Math.min(nChan, 4);
if (rleEncoded) {
// get list of rle encoded line lengths for all channels
readLineLengths(height * nc);
}
layer.nChan = nc;
layer.chanID = new int[nc];
for (int i = 0; i < nc; i++) {
int id = i;
if (i == 3) id = -1;
layer.chanID[i] = id;
}
}
protected void readLineLengths(int nLines) {
// readData list of rle encoded line lengths
lineLengths = new short[nLines];
for (int i = 0; i < nLines; i++) {
lineLengths[i] = readShort();
}
lineIndex = 0;
}
protected BufferedImage makeImage(int w, int h, byte[] r, byte[] g, byte[] b, byte[] a) {
// create image from given plane data
BufferedImage im = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
int[] data = ((DataBufferInt) im.getRaster().getDataBuffer()).getData();
int n = w * h;
int j = 0;
while (j < n) {
try {
int ac = a[j] & 0xff;
int rc = r[j] & 0xff;
int gc = g[j] & 0xff;
int bc = b[j] & 0xff;
data[j] = (((((ac << 8) | rc) << 8) | gc) << 8) | bc;
} catch (Exception e) {}
j++;
}
return im;
}
protected void process() {
// decode PSD file
if (err()) return;
readHeader();
if (err()) return;
readLayerInfo();
if (err()) return;
if (nLayers == 0) {
makeDummyLayer();
if (err()) return;
}
readLayers();
}
protected int readByte() {
// readData single byte from input
int curByte = 0;
try {
curByte = input.read();
} catch (IOException e) {
status = STATUS_FORMAT_ERROR;
}
return curByte;
}
protected int readBytes(byte[] bytes, int n) {
// readData multiple bytes from input
if (bytes == null) return 0;
int r = 0;
try {
r = input.read(bytes, 0, n);
} catch (IOException e) {
status = STATUS_FORMAT_ERROR;
}
if (r < n) {
status = STATUS_FORMAT_ERROR;
}
return r;
}
protected void readHeader() {
// readData PSD header info
String sig = readString(4);
int ver = readShort();
skipBytes(6);
nChan = readShort();
height = readInt();
width = readInt();
int depth = readShort();
int mode = readShort();
int cmLen = readInt();
skipBytes(cmLen);
int imResLen = readInt();
skipBytes(imResLen);
// require 8-bit RGB data
if ((!sig.equals("8BPS")) || (ver != 1)) {
status = STATUS_FORMAT_ERROR;
} else if ((depth != 8) || (mode != 3)) {
status = STATUS_UNSUPPORTED;
}
}
protected int readInt() {
// readData big-endian 32-bit integer
return (((((readByte() << 8) | readByte()) << 8) | readByte()) << 8)
| readByte();
}
protected void readLayerInfo() {
// readData layer header info
miscLen = readInt();
if (miscLen == 0) {
return; // no layers, only base image
}
int layerInfoLen = readInt();
nLayers = readShort();
if (nLayers > 0) {
layers = new LayerInfo[nLayers];
}
for (int i = 0; i < nLayers; i++) {
LayerInfo info = new LayerInfo();
layers[i] = info;
info.y = readInt();
info.x = readInt();
info.h = readInt() - info.y;
info.w = readInt() - info.x;
info.nChan = readShort();
info.chanID = new int[info.nChan];
for (int j = 0; j < info.nChan; j++) {
int id = readShort();
int size = readInt();
info.chanID[j] = id;
}
String s = readString(4);
if (!s.equals("8BIM")) {
status = STATUS_FORMAT_ERROR;
return;
}
skipBytes(4); // blend mode
info.alpha = readByte();
int clipping = readByte();
int flags = readByte();
readByte(); // filler
int extraSize = readInt();
skipBytes(extraSize);
}
}
protected void readLayers() {
// readData and convert each layer to BufferedImage
frameCount = nLayers;
frames = new BufferedImage[nLayers];
for (int i = 0; i < nLayers; i++) {
LayerInfo info = layers[i];
byte[] r = null, g = null, b = null, a = null;
for (int j = 0; j < info.nChan; j++) {
int id = info.chanID[j];
switch (id) {
case 0 : r = readPlane(info.w, info.h); break;
case 1 : g = readPlane(info.w, info.h); break;
case 2 : b = readPlane(info.w, info.h); break;
case -1 : a = readPlane(info.w, info.h); break;
default : readPlane(info.w, info.h);
}
if (err()) break;
}
if (err()) break;
int n = info.w * info.h;
if (r == null) r = fillBytes(n, 0);
if (g == null) g = fillBytes(n, 0);
if (b == null) b = fillBytes(n, 0);
if (a == null) a = fillBytes(n, 255);
BufferedImage im = makeImage(info.w, info.h, r, g, b, a);
frames[i] = im;
}
lineLengths = null;
if ((miscLen > 0) && !err()) {
int n = readInt(); // global layer mask info len
skipBytes(n);
}
}
protected byte[] readPlane(int w, int h) {
// readData a single color plane
byte[] b = null;
int size = w * h;
if (hasLayers) {
// get RLE compression info for channel
rleEncoded = readShort() == 1;
if (rleEncoded) {
// list of encoded line lengths
readLineLengths(h);
}
}
if (rleEncoded) {
b = readPlaneCompressed(w, h);
} else {
b = new byte[size];
readBytes(b, size);
}
return b;
}
protected byte[] readPlaneCompressed(int w, int h) {
byte[] b = new byte[w * h];
byte[] s = new byte[w * 2];
int pos = 0;
for (int i = 0; i < h; i++) {
if (lineIndex >= lineLengths.length) {
status = STATUS_FORMAT_ERROR;
return null;
}
int len = lineLengths[lineIndex++];
readBytes(s, len);
decodeRLE(s, 0, len, b, pos);
pos += w;
}
return b;
}
protected void decodeRLE(byte[] src, int sindex, int slen, byte[] dst, int dindex) {
try {
int max = sindex + slen;
while (sindex < max) {
byte b = src[sindex++];
int n = (int) b;
if (n < 0) {
// dup next byte 1-n times
n = 1 - n;
b = src[sindex++];
for (int i = 0; i < n; i++) {
dst[dindex++] = b;
}
} else {
// copy next n+1 bytes
n = n + 1;
System.arraycopy(src, sindex, dst, dindex, n);
dindex += n;
sindex += n;
}
}
} catch (Exception e) {
status = STATUS_FORMAT_ERROR;
}
}
protected short readShort() {
// readData big-endian 16-bit integer
return (short) ((readByte() << 8) | readByte());
}
protected String readString(int len) {
// readData string of specified length
String s = "";
for (int i = 0; i < len; i++) {
s = s + (char) readByte();
}
return s;
}
protected void skipBytes(int n) {
// skip over n input bytes
for (int i = 0; i < n; i++) {
readByte();
}
}
}
@@ -0,0 +1,126 @@
/*
* Copyright (c) 2008, 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 "TwelveMonkeys" 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 OWNER 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.plugins.psd;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.IIOException;
import java.io.IOException;
/**
* PSDResolutionInfo
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDResolutionInfo.java,v 1.0 May 2, 2008 3:58:19 PM haraldk Exp$
*/
class PSDResolutionInfo extends PSDImageResource {
// typedef struct _ResolutionInfo
// {
// LONG hRes; /* Fixed-point number: pixels per inch */
// WORD hResUnit; /* 1=pixels per inch, 2=pixels per centimeter */
// WORD WidthUnit; /* 1=in, 2=cm, 3=pt, 4=picas, 5=columns */
// LONG vRes; /* Fixed-point number: pixels per inch */
// WORD vResUnit; /* 1=pixels per inch, 2=pixels per centimeter */
// WORD HeightUnit; /* 1=in, 2=cm, 3=pt, 4=picas, 5=columns */
// } RESOLUTIONINFO;
private float mHRes;
private short mHResUnit;
private short mWidthUnit;
private float mVRes;
private short mVResUnit;
private short mHeightUnit;
PSDResolutionInfo(final short pId, final ImageInputStream pInput) throws IOException {
super(pId, pInput);
}
@Override
protected void readData(ImageInputStream pInput) throws IOException {
if (mSize != 16) {
throw new IIOException("Resolution info length expected to be 16: " + mSize);
}
mHRes = PSDUtil.fixedPointToFloat(pInput.readInt());
mHResUnit = pInput.readShort();
mWidthUnit = pInput.readShort();
mVRes = PSDUtil.fixedPointToFloat(pInput.readInt());
mVResUnit = pInput.readShort();
mHeightUnit = pInput.readShort();
}
@Override
public String toString() {
StringBuilder builder = toStringBuilder();
builder.append(", hRes: ").append(mHRes);
builder.append(" ");
builder.append(resUnit(mHResUnit));
builder.append(", width unit: ");
builder.append(dimUnit(mWidthUnit));
builder.append(", vRes: ").append(mVRes);
builder.append(" ");
builder.append(resUnit(mVResUnit));
builder.append(", height unit: ");
builder.append(dimUnit(mHeightUnit));
builder.append("]");
return builder.toString();
}
private String resUnit(final short pResUnit) {
switch (pResUnit) {
case 1:
return "pixels/inch";
case 2:
return "pixels/cm";
default:
return "unknown unit " + pResUnit;
}
}
private String dimUnit(final short pUnit) {
switch (pUnit) {
case 1:
return "in";
case 2:
return "cm";
case 3:
return "pt";
case 4:
return "pica";
case 5:
return "column";
default:
return "unknown unit " + pUnit;
}
}
}
@@ -0,0 +1,86 @@
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import java.awt.image.BufferedImage;
import java.io.IOException;
/**
* PSDThumbnail
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDThumbnail.java,v 1.0 Jul 29, 2009 4:41:06 PM haraldk Exp$
*/
class PSDThumbnail extends PSDImageResource {
private BufferedImage mThumbnail;
public PSDThumbnail(final short pId, final ImageInputStream pInput) throws IOException {
super(pId, pInput);
}
/*
Thumbnail header, size 28
4 Format. 1 = kJpegRGB . Also supports kRawRGB (0).
4 Width of thumbnail in pixels.
4 Height of thumbnail in pixels.
4 Widthbytes: Padded row bytes = (width * bits per pixel + 31) / 32 * 4.
4 Total size = widthbytes * height * planes
4 Size after compression. Used for consistency check.
2 Bits per pixel. = 24
2 Number of planes. = 1
*/
@Override
protected void readData(final ImageInputStream pInput) throws IOException {
// TODO: Support for RAW RGB (format == 0)
int format = pInput.readInt();
switch (format) {
case 0:
throw new IIOException("RAW RGB format thumbnail not supported yet");
case 1:
break;
default:
throw new IIOException(String.format("Unsupported thumbnail format (%s) in PSD document", format));
}
// This data isn't really useful, unless we're dealing with raw bytes
int width = pInput.readInt();
int height = pInput.readInt();
int widthBytes = pInput.readInt();
int totalSize = pInput.readInt();
// Consistency check
int sizeCompressed = pInput.readInt();
if (sizeCompressed != (mSize - 28)) {
throw new IIOException("Corrupt thumbnail in PSD document");
}
// According to the spec, only 24 bits and 1 plane is supported
int bits = pInput.readUnsignedShort();
int planes = pInput.readUnsignedShort();
if (bits != 24 && planes != 1) {
// TODO: Warning/Exception
}
// TODO: Support BGR if id == RES_THUMBNAIL_PS4? Or is that already supported in the JPEG?
mThumbnail = ImageIO.read(IIOUtil.createStreamAdapter(pInput, sizeCompressed));
}
public BufferedImage getThumbnail() {
return mThumbnail;
}
@Override
public String toString() {
StringBuilder builder = toStringBuilder();
builder.append(", ").append(mThumbnail);
builder.append("]");
return builder.toString();
}
}
@@ -0,0 +1,94 @@
/*
* Copyright (c) 2008, 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 "TwelveMonkeys" 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 OWNER 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.plugins.psd;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.io.enc.InflateDecoder;
import com.twelvemonkeys.io.enc.PackBitsDecoder;
import javax.imageio.stream.ImageInputStream;
import java.io.DataInputStream;
import java.io.IOException;
/**
* PSDUtil
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDUtil.java,v 1.0 Apr 29, 2008 5:05:00 PM haraldk Exp$
*/
final class PSDUtil {
// TODO: Duplicated code from IFF plugin, move to some common util?
static String intToStr(int pChunkId) {
return new String(
new byte[]{
(byte) ((pChunkId & 0xff000000) >> 24),
(byte) ((pChunkId & 0x00ff0000) >> 16),
(byte) ((pChunkId & 0x0000ff00) >> 8),
(byte) ((pChunkId & 0x000000ff))
}
);
}
// TODO: Proably also useful for PICT reader, move to some common util?
static String readPascalString(ImageInputStream pInput) throws IOException {
int length = pInput.readUnsignedByte();
// int length = pInput.readUnsignedShort();
byte[] bytes = new byte[length];
pInput.readFully(bytes);
if (length % 2 == 0) {
pInput.readByte(); // Pad
}
return new String(bytes);
}
static String readPascalStringByte(ImageInputStream pInput) throws IOException {
int length = pInput.readUnsignedByte();
byte[] bytes = new byte[length];
pInput.readFully(bytes);
return new String(bytes);
}
static DataInputStream createPackBitsStream(final ImageInputStream pInput, int pLength) {
return new DataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(pInput, pLength), new PackBitsDecoder()));
}
static DataInputStream createZipStream(final ImageInputStream pInput, int pLength) {
return new DataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(pInput, pLength), new InflateDecoder()));
}
static DataInputStream createZipPredictorStream(final ImageInputStream pInput, int pLength) {
throw new UnsupportedOperationException("Method createZipPredictonStream not implemented");
}
public static float fixedPointToFloat(int pFP) {
return ((pFP & 0xffff0000) >> 16) + (pFP & 0xffff) / (float) 0xffff;
}
}
@@ -0,0 +1,60 @@
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.lang.StringUtil;
import javax.imageio.stream.ImageInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
/**
* XMP metadata.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: XMPData.java,v 1.0 Jul 28, 2009 5:50:34 PM haraldk Exp$
*
* @see <a href="http://www.adobe.com/products/xmp/">Adobe Extensible Metadata Platform (XMP)</a>
* @see <a href="http://www.adobe.com/devnet/xmp/">Adobe XMP Developer Center</a>
*/
public final class PSDXMPData extends PSDImageResource {
protected byte[] mData;
PSDXMPData(final short pId, final ImageInputStream pInput) throws IOException {
super(pId, pInput);
}
@Override
protected void readData(final ImageInputStream pInput) throws IOException {
mData = new byte[(int) mSize]; // TODO: Fix potential overflow, or document why that can't happen (read spec)
pInput.readFully(mData);
}
@Override
public String toString() {
StringBuilder builder = toStringBuilder();
int length = Math.min(256, mData.length);
String data = StringUtil.decode(mData, 0, length, "UTF-8").replace('\n', ' ').replaceAll("\\s+", " ");
builder.append(", data: \"").append(data);
if (length < mData.length) {
builder.append("...");
}
builder.append("\"]");
return builder.toString();
}
/**
* Returns a character stream containing the XMP metadata (XML).
*
* @return the XMP metadata.
*/
public Reader getData() {
return new InputStreamReader(new ByteArrayInputStream(mData), Charset.forName("UTF-8"));
}
}
@@ -0,0 +1,122 @@
/*
* Copyright (c) 2008, 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 "TwelveMonkeys" 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 OWNER 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.plugins.psd;
import java.awt.color.ColorSpace;
/**
* YCbCrColorSpace
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: YCbCrColorSpace.java,v 1.0 Jun 28, 2008 3:30:50 PM haraldk Exp$
*/
// TODO: Move to com.twlevemonkeys.image?
// TODO: Read an ICC YCbCr profile from classpath resource? Is there such a thing?
final class YCbCrColorSpace extends ColorSpace {
static final ColorSpace INSTANCE = new CMYKColorSpace();
final ColorSpace sRGB = getInstance(CS_sRGB);
YCbCrColorSpace() {
super(ColorSpace.TYPE_YCbCr, 3);
}
public static ColorSpace getInstance() {
return INSTANCE;
}
// http://www.w3.org/Graphics/JPEG/jfif.txt
/*
Conversion to and from RGB
Y, Cb, and Cr are converted from R, G, and B as defined in CCIR Recommendation 601
but are normalized so as to occupy the full 256 levels of a 8-bit binary encoding. More
precisely:
Y = 256 * E'y
Cb = 256 * [ E'Cb ] + 128
Cr = 256 * [ E'Cr ] + 128
where the E'y, E'Cb and E'Cb are defined as in CCIR 601. Since values of E'y have a
range of 0 to 1.0 and those for E'Cb and E'Cr have a range of -0.5 to +0.5, Y, Cb, and Cr
must be clamped to 255 when they are maximum value.
RGB to YCbCr Conversion
YCbCr (256 levels) can be computed directly from 8-bit RGB as follows:
Y = 0.299 R + 0.587 G + 0.114 B
Cb = - 0.1687 R - 0.3313 G + 0.5 B + 128
Cr = 0.5 R - 0.4187 G - 0.0813 B + 128
NOTE - Not all image file formats store image samples in the order R0, G0,
B0, ... Rn, Gn, Bn. Be sure to verify the sample order before converting an
RGB file to JFIF.
YCbCr to RGB Conversion
RGB can be computed directly from YCbCr (256 levels) as follows:
R = Y + 1.402 (Cr-128)
G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128)
B = Y + 1.772 (Cb-128)
*/
public float[] toRGB(float[] colorvalue) {
// R = Y + 1.402 (Cr-128)
// G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128)
// B = Y + 1.772 (Cb-128)
return new float[] {
colorvalue[0] + 1.402f * (colorvalue[2] - .5f),
colorvalue[0] - 0.34414f * (colorvalue[1] - .5f) - 0.71414f * (colorvalue[2] - .5f),
colorvalue[0] + 1.772f * (colorvalue[1] - .5f),
};
// TODO: Convert via CIEXYZ space using sRGB space, as suggested in docs
// return sRGB.fromCIEXYZ(toCIEXYZ(colorvalue));
}
public float[] fromRGB(float[] rgbvalue) {
// Y = 0.299 R + 0.587 G + 0.114 B
// Cb = - 0.1687 R - 0.3313 G + 0.5 B + 128
// Cr = 0.5 R - 0.4187 G - 0.0813 B + 128
return new float[] {
0.299f * rgbvalue[0] + 0.587f * rgbvalue[1] + 0.114f * rgbvalue[2],
-0.1687f * rgbvalue[0] - 0.3313f * rgbvalue[1] + 0.5f * rgbvalue[2] + .5f,
0.5f * rgbvalue[0] - 0.4187f * rgbvalue[1] - 0.0813f * rgbvalue[2] + .5f
};
}
public float[] toCIEXYZ(float[] colorvalue) {
throw new UnsupportedOperationException("Method toCIEXYZ not implemented"); // TODO: Implement
}
public float[] fromCIEXYZ(float[] colorvalue) {
throw new UnsupportedOperationException("Method fromCIEXYZ not implemented"); // TODO: Implement
}
}
@@ -0,0 +1 @@
com.twelvemonkeys.imageio.plugins.psd.PSDImageReaderSpi
@@ -0,0 +1,68 @@
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.util.Arrays;
import java.util.List;
/**
* PSDImageReaderTestCase
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
*/
public class PSDImageReaderTestCase extends ImageReaderAbstractTestCase<PSDImageReader> {
static ImageReaderSpi sProvider = new PSDImageReaderSpi();
protected List<TestData> getTestData() {
return Arrays.asList(
// 5 channel, RGB
new TestData(getClassLoaderResource("/psd/photoshopping.psd"), new Dimension(300, 225)),
// 1 channel, gray, 8 bit samples
new TestData(getClassLoaderResource("/psd/buttons.psd"), new Dimension(20, 20)),
// 5 channel, CMYK
new TestData(getClassLoaderResource("/psd/escenic-liquid-logo.psd"), new Dimension(595, 420)),
// 3 channel RGB, no composite layer
new TestData(getClassLoaderResource("/psd/jugware-icon.psd"), new Dimension(128, 128)),
// 3 channel RGB, old data, no layer info/mask
new TestData(getClassLoaderResource("/psd/MARBLES.PSD"), new Dimension(1419, 1001)),
// 1 channel, indexed color
new TestData(getClassLoaderResource("/psd/coral_fish.psd"), new Dimension(800, 800))
// 1 channel, bitmap, 1 bit samples
// new TestData(getClassLoaderResource("/psd/test_bitmap.psd"), new Dimension(800, 600))
// 1 channel, gray, 16 bit samples
// new TestData(getClassLoaderResource("/psd/test_gray16.psd"), new Dimension(800, 600))
// TODO: Need uncompressed PSD
// TODO: Need more recent ZIP compressed PSD files from CS2/CS3+
);
}
protected ImageReaderSpi createProvider() {
return sProvider;
}
@Override
protected PSDImageReader createReader() {
return new PSDImageReader(sProvider);
}
protected Class<PSDImageReader> getReaderClass() {
return PSDImageReader.class;
}
protected List<String> getFormatNames() {
return Arrays.asList("psd");
}
protected List<String> getSuffixes() {
return Arrays.asList("psd");
}
protected List<String> getMIMETypes() {
return Arrays.asList("image/x-psd");
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.