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 }