1 module psd.parse.sections.imgres; 2 import psd.parse; 3 import psd.parse.io; 4 import std.exception; 5 6 // 7 // IMAGE RESOURCES 8 // 9 10 /** 11 An image resource block 12 */ 13 struct ImageResourceBlock { 14 /** 15 Unique ID 16 */ 17 ushort uid; 18 19 /** 20 Name of resource 21 */ 22 string name; 23 24 /** 25 Data of resource 26 */ 27 ubyte[] data; 28 } 29 30 /** 31 A struct representing a thumbnail as stored in the image resources section. 32 */ 33 struct Thumbnail 34 { 35 uint width; 36 uint height; 37 uint binaryJpegSize; 38 ubyte[] binaryJpeg; 39 } 40 41 /** 42 A struct representing an alpha channel as stored in the image resources section. 43 NOTE: Note that the image data for alpha channels is stored in the image data section. 44 */ 45 struct AlphaChannel 46 { 47 enum Mode : ubyte 48 { 49 ALPHA = 0, // The channel stores alpha data. 50 INVERTED_ALPHA = 1, // The channel stores inverted alpha data. 51 SPOT = 2 // The channel stores spot color data. 52 } 53 54 /** 55 The channel's ASCII name. 56 */ 57 string asciiName; 58 59 /** 60 The color space the colors are stored in. 61 */ 62 ushort colorSpace; 63 64 /** 65 16-bit color data with 0 being black and 65535 being white (assuming RGBA). 66 */ 67 ushort[4] color; 68 69 /** 70 The channel's opacity in the range [0, 100]. 71 */ 72 ushort opacity; 73 74 /** 75 The channel's mode, one of AlphaChannel::Mode. 76 */ 77 Mode mode; 78 } 79 80 /** 81 A struct representing the information extracted from the Image Resources section. 82 */ 83 struct ImageResourcesData 84 { 85 /** 86 An array of alpha channels, having alphaChannelCount entries. 87 */ 88 AlphaChannel[] alphaChannels; 89 90 /** 91 The number of alpha channels stored in the array. 92 */ 93 uint alphaChannelCount; 94 95 /** 96 Raw data of the ICC profile. 97 */ 98 ubyte[] iccProfile; 99 uint sizeOfICCProfile; 100 101 /** 102 Raw EXIF data. 103 */ 104 ubyte[] exifData; 105 uint sizeOfExifData; 106 107 /** 108 Whether the PSD contains real merged data. 109 */ 110 bool containsRealMergedData; 111 112 /** 113 Raw XMP metadata. 114 */ 115 ubyte[] xmpMetadata; 116 117 /** 118 JPEG thumbnail. 119 */ 120 Thumbnail thumbnail; 121 } 122 123 124 enum ImageResourceType 125 { 126 IPTC_NAA = 1028, 127 CAPTION_DIGEST = 1061, 128 XMP_METADATA = 1060, 129 PRINT_INFORMATION = 1082, 130 PRINT_STYLE = 1083, 131 PRINT_SCALE = 1062, 132 PRINT_FLAGS = 1011, 133 PRINT_FLAGS_INFO = 10000, 134 PRINT_INFO = 1071, 135 RESOLUTION_INFO = 1005, 136 DISPLAY_INFO = 1077, 137 GLOBAL_ANGLE = 1037, 138 GLOBAL_ALTITUDE = 1049, 139 COLOR_HALFTONING_INFO = 1013, 140 COLOR_TRANSFER_FUNCTIONS = 1016, 141 MULTICHANNEL_HALFTONING_INFO = 1012, 142 MULTICHANNEL_TRANSFER_FUNCTIONS = 1015, 143 LAYER_STATE_INFORMATION = 1024, 144 LAYER_GROUP_INFORMATION = 1026, 145 LAYER_GROUP_ENABLED_ID = 1072, 146 LAYER_SELECTION_ID = 1069, 147 GRID_GUIDES_INFO = 1032, 148 URL_LIST = 1054, 149 SLICES = 1050, 150 PIXEL_ASPECT_RATIO = 1064, 151 ICC_PROFILE = 1039, 152 ICC_UNTAGGED_PROFILE = 1041, 153 ID_SEED_NUMBER = 1044, 154 THUMBNAIL_RESOURCE = 1036, 155 VERSION_INFO = 1057, 156 EXIF_DATA = 1058, 157 BACKGROUND_COLOR = 1010, 158 ALPHA_CHANNEL_ASCII_NAMES = 1006, 159 ALPHA_CHANNEL_UNICODE_NAMES = 1045, 160 ALPHA_IDENTIFIERS = 1053, 161 COPYRIGHT_FLAG = 1034, 162 PATH_SELECTION_STATE = 1088, 163 ONION_SKINS = 1078, 164 TIMELINE_INFO = 1075, 165 SHEET_DISCLOSURE = 1076, 166 WORKING_PATH = 1025, 167 MAC_PRINT_MANAGER_INFO = 1001, 168 WINDOWS_DEVMODE = 1085 169 } 170 171 /** 172 Parses image resources 173 */ 174 void parseImageResources(ref ParserContext ctx) { 175 ctx.seek(ctx.psd.imageResourcesSection.offset); 176 ulong leftToRead = ctx.psd.imageResourcesSection.length; 177 178 while (leftToRead > 0) { 179 string signature = ctx.readStr(4); 180 181 enforce(signature == "8BIM" || signature == "psdM", 182 "Image resources section seems to be corrupt, signature does not match \"8BIM\" nor \"psdM\"."); 183 184 const ushort id = ctx.readValue!ushort; 185 186 const ubyte nameLength = ctx.readPaddedValue!ubyte(2, 1); 187 const string name = ctx.readStr(nameLength - 1); 188 189 const uint resourceSize = ctx.readPaddedValue!uint(); 190 191 switch (id) { 192 case ImageResourceType.IPTC_NAA: 193 case ImageResourceType.CAPTION_DIGEST: 194 case ImageResourceType.PRINT_INFORMATION: 195 case ImageResourceType.PRINT_STYLE: 196 case ImageResourceType.PRINT_SCALE: 197 case ImageResourceType.PRINT_FLAGS: 198 case ImageResourceType.PRINT_FLAGS_INFO: 199 case ImageResourceType.PRINT_INFO: 200 case ImageResourceType.RESOLUTION_INFO: 201 case ImageResourceType.GLOBAL_ANGLE: 202 case ImageResourceType.GLOBAL_ALTITUDE: 203 case ImageResourceType.COLOR_HALFTONING_INFO: 204 case ImageResourceType.COLOR_TRANSFER_FUNCTIONS: 205 case ImageResourceType.MULTICHANNEL_HALFTONING_INFO: 206 case ImageResourceType.MULTICHANNEL_TRANSFER_FUNCTIONS: 207 case ImageResourceType.LAYER_STATE_INFORMATION: 208 case ImageResourceType.LAYER_GROUP_INFORMATION: 209 case ImageResourceType.LAYER_GROUP_ENABLED_ID: 210 case ImageResourceType.LAYER_SELECTION_ID: 211 case ImageResourceType.GRID_GUIDES_INFO: 212 case ImageResourceType.URL_LIST: 213 case ImageResourceType.SLICES: 214 case ImageResourceType.PIXEL_ASPECT_RATIO: 215 case ImageResourceType.ICC_UNTAGGED_PROFILE: 216 case ImageResourceType.ID_SEED_NUMBER: 217 case ImageResourceType.BACKGROUND_COLOR: 218 case ImageResourceType.ALPHA_CHANNEL_UNICODE_NAMES: 219 case ImageResourceType.ALPHA_IDENTIFIERS: 220 case ImageResourceType.COPYRIGHT_FLAG: 221 case ImageResourceType.PATH_SELECTION_STATE: 222 case ImageResourceType.ONION_SKINS: 223 case ImageResourceType.TIMELINE_INFO: 224 case ImageResourceType.SHEET_DISCLOSURE: 225 case ImageResourceType.WORKING_PATH: 226 case ImageResourceType.MAC_PRINT_MANAGER_INFO: 227 case ImageResourceType.WINDOWS_DEVMODE: 228 // we are currently not interested in this resource type, skip it 229 default: 230 // this is a resource we know nothing about, so skip it 231 ctx.skip(resourceSize); 232 break; 233 234 case ImageResourceType.DISPLAY_INFO: 235 { 236 // the display info resource stores color information and opacity for extra channels contained 237 // in the document. these extra channels could be alpha/transparency, as well as spot color 238 // channels used for printing. 239 240 // check whether storage for alpha channels has been allocated yet 241 // (ImageResourceType.ALPHA_CHANNEL_ASCII_NAMES stores the channel names) 242 if (ctx.psd.imageResourcesData.alphaChannels.length == 0) 243 { 244 // note that this assumes RGB mode 245 const uint channelCount = ctx.psd.header.channels - 3; 246 ctx.psd.imageResourcesData.alphaChannelCount = channelCount; 247 ctx.psd.imageResourcesData.alphaChannels.length = channelCount; 248 } 249 250 const uint versionNum = ctx.readValue!uint; 251 252 for (uint i = 0u; i < ctx.psd.imageResourcesData.alphaChannelCount; ++i) { 253 AlphaChannel* channel = &ctx.psd.imageResourcesData.alphaChannels[i]; 254 channel.colorSpace = ctx.readValue!ushort; 255 channel.color[0] = ctx.readValue!ushort; 256 channel.color[1] = ctx.readValue!ushort; 257 channel.color[2] = ctx.readValue!ushort; 258 channel.color[3] = ctx.readValue!ushort; 259 channel.opacity = ctx.readValue!ushort; 260 channel.mode = cast(AlphaChannel.Mode)ctx.readValue!ubyte; 261 } 262 } 263 break; 264 265 case ImageResourceType.VERSION_INFO: 266 { 267 const uint versionNum = ctx.readValue!uint; 268 const ubyte hasRealMergedData = ctx.readValue!ubyte; 269 ctx.psd.imageResourcesData.containsRealMergedData = (hasRealMergedData != 0u); 270 ctx.skip(resourceSize - 5u); 271 } 272 break; 273 274 case ImageResourceType.THUMBNAIL_RESOURCE: 275 { 276 const uint format = ctx.readValue!uint; 277 const uint width = ctx.readValue!uint; 278 const uint height = ctx.readValue!uint; 279 const uint widthInBytes = ctx.readValue!uint; 280 const uint totalSize = ctx.readValue!uint; 281 const uint binaryJpegSize = ctx.readValue!uint; 282 283 const ushort bitsPerPixel = ctx.readValue!ushort; 284 const ushort numberOfPlanes = ctx.readValue!ushort; 285 286 ctx.psd.imageResourcesData.thumbnail.width = width; 287 ctx.psd.imageResourcesData.thumbnail.height = height; 288 ctx.psd.imageResourcesData.thumbnail.binaryJpegSize = binaryJpegSize; 289 ctx.psd.imageResourcesData.thumbnail.binaryJpeg = ctx.readData(binaryJpegSize); 290 291 const uint bytesToSkip = resourceSize - 28u - binaryJpegSize; 292 ctx.skip(bytesToSkip); 293 } 294 break; 295 296 case ImageResourceType.XMP_METADATA: 297 { 298 // load the XMP metadata as raw data 299 enforce(ctx.psd.imageResourcesData.xmpMetadata.length != 0, "File contains more than one XMP metadata resource."); 300 ctx.psd.imageResourcesData.xmpMetadata = ctx.readData(resourceSize); 301 } 302 break; 303 304 case ImageResourceType.ICC_PROFILE: 305 { 306 // load the ICC profile as raw data 307 enforce(ctx.psd.imageResourcesData.iccProfile.length != 0, "File contains more than one ICC profile."); 308 ctx.psd.imageResourcesData.iccProfile = ctx.readData(resourceSize); 309 ctx.psd.imageResourcesData.sizeOfICCProfile = resourceSize; 310 } 311 break; 312 313 case ImageResourceType.EXIF_DATA: 314 { 315 // load the EXIF data as raw data 316 enforce(ctx.psd.imageResourcesData.exifData.length != 0, "File contains more than one EXIF data block."); 317 ctx.psd.imageResourcesData.exifData = ctx.readData(resourceSize); 318 ctx.psd.imageResourcesData.sizeOfExifData = resourceSize; 319 } 320 break; 321 322 case ImageResourceType.ALPHA_CHANNEL_ASCII_NAMES: 323 { 324 // check whether storage for alpha channels has been allocated yet 325 // (ImageResourceType.DISPLAY_INFO stores the channel color data) 326 if (ctx.psd.imageResourcesData.alphaChannels.length == 0) 327 { 328 // note that this assumes RGB mode 329 const uint channelCount = ctx.psd.header.channels - 3; 330 ctx.psd.imageResourcesData.alphaChannelCount = channelCount; 331 ctx.psd.imageResourcesData.alphaChannels.length = channelCount; 332 } 333 334 // the names of the alpha channels are stored as a series of Pascal strings 335 uint channel = 0; 336 long remaining = resourceSize; 337 while (remaining > 0) { 338 string channelName; 339 const ubyte channelNameLength = ctx.readValue!ubyte; 340 if (channelNameLength > 0) { 341 channelName = ctx.readStr(channelNameLength); 342 } 343 344 remaining -= 1 + channelNameLength; 345 346 if (channel < ctx.psd.imageResourcesData.alphaChannelCount) { 347 ctx.psd.imageResourcesData.alphaChannels[channel].asciiName = channelName; 348 ++channel; 349 } 350 } 351 } 352 break; 353 } 354 355 leftToRead -= 10 + nameLength + resourceSize; 356 } 357 }