/src/exiv2/src/psdimage.cpp
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | |
3 | | // included header files |
4 | | #include "psdimage.hpp" |
5 | | |
6 | | #include "basicio.hpp" |
7 | | #include "config.h" |
8 | | #include "enforce.hpp" |
9 | | #include "error.hpp" |
10 | | #include "futils.hpp" |
11 | | #include "image.hpp" |
12 | | #include "photoshop.hpp" |
13 | | |
14 | | #ifdef EXIV2_DEBUG_MESSAGES |
15 | | #include <iostream> |
16 | | #endif |
17 | | |
18 | | // Todo: Consolidate with existing code in struct Photoshop (jpgimage.hpp): |
19 | | // Extend this helper to a proper class with all required functionality, |
20 | | // then move it here or into a separate file? |
21 | | |
22 | | //! @cond IGNORE |
23 | | struct PhotoshopResourceBlock { |
24 | | uint32_t resourceType; // one of the markers in Photoshop::irbId_[] |
25 | | uint16_t resourceId; |
26 | | unsigned char resourceName[2]; // Pascal string (length byte + characters), padded to an even size -- this assumes |
27 | | // the empty string |
28 | | uint32_t resourceDataSize; |
29 | | }; |
30 | | //! @endcond |
31 | | |
32 | | // Photoshop resource IDs (Cf. |
33 | | // <http://search.cpan.org/~bettelli/Image-MetaData-JPEG-0.15/lib/Image/MetaData/JPEG/TagLists.pod>) |
34 | | enum kPhotoshopResourceID { |
35 | | Photoshop2Info = 0x03e8, // [obsolete -- Photoshop 2.0 only] General information -- contains five 2-byte values: |
36 | | // number of channels, rows, columns, depth and mode |
37 | | MacintoshClassicPrintInfo = 0x03e9, // [optional] Macintosh classic print record (120 bytes) |
38 | | MacintoshCarbonPrintInfo = 0x03ea, // [optional] Macintosh carbon print info (variable-length XML format) |
39 | | Photoshop2ColorTable = 0x03eb, // [obsolete -- Photoshop 2.0 only] Indexed color table |
40 | | ResolutionInfo = 0x03ed, // PhotoshopResolutionInfo structure (see below) |
41 | | AlphaChannelsNames = 0x03ee, // as a series of Pstrings |
42 | | DisplayInfo = 0x03ef, // see appendix A in Photoshop SDK |
43 | | PStringCaption = 0x03f0, // [optional] the caption, as a Pstring |
44 | | BorderInformation = 0x03f1, // border width and units |
45 | | BackgroundColor = 0x03f2, // see additional Adobe information |
46 | | PrintFlags = 0x03f3, // labels, crop marks, colour bars, ecc... |
47 | | BWHalftoningInfo = 0x03f4, // Gray-scale and multich. half-toning info |
48 | | ColorHalftoningInfo = 0x03f5, // Colour half-toning information |
49 | | DuotoneHalftoningInfo = 0x03f6, // Duo-tone half-toning information |
50 | | BWTransferFunc = 0x03f7, // Gray-scale and multich. transfer function |
51 | | ColorTransferFuncs = 0x03f8, // Colour transfer function |
52 | | DuotoneTransferFuncs = 0x03f9, // Duo-tone transfer function |
53 | | DuotoneImageInfo = 0x03fa, // Duo-tone image information |
54 | | EffectiveBW = 0x03fb, // two bytes for the effective black and white values |
55 | | ObsoletePhotoshopTag1 = 0x03fc, // [obsolete] |
56 | | EPSOptions = 0x03fd, // Encapsulated Postscript options |
57 | | QuickMaskInfo = 0x03fe, // Quick Mask information. 2 bytes containing Quick Mask channel ID, |
58 | | // 1 byte boolean indicating whether the mask was initially empty. |
59 | | ObsoletePhotoshopTag2 = 0x03ff, // [obsolete] |
60 | | LayerStateInfo = 0x0400, // index of target layer (0 means bottom) |
61 | | WorkingPathInfo = 0x0401, // should not be saved to the file |
62 | | LayersGroupInfo = 0x0402, // for grouping layers together |
63 | | ObsoletePhotoshopTag3 = 0x0403, // [obsolete] ?? |
64 | | IPTC_NAA = 0x0404, // IPTC/NAA data |
65 | | RawImageMode = 0x0405, // image mode for raw format files |
66 | | JPEGQuality = 0x0406, // [private] |
67 | | GridGuidesInfo = 0x0408, // see additional Adobe information |
68 | | ThumbnailResource = 0x0409, // see additional Adobe information |
69 | | CopyrightFlag = 0x040a, // true if image is copyrighted |
70 | | URL = 0x040b, // text string with a resource locator |
71 | | ThumbnailResource2 = 0x040c, // see additional Adobe information |
72 | | GlobalAngle = 0x040d, // global lighting angle for effects layer |
73 | | ColorSamplersResource = 0x040e, // see additional Adobe information |
74 | | ICCProfile = 0x040f, // see notes from Internat. Color Consortium |
75 | | Watermark = 0x0410, // one byte |
76 | | ICCUntagged = 0x0411, // 1 means intentionally untagged |
77 | | EffectsVisible = 0x0412, // 1 byte to show/hide all effects layers |
78 | | SpotHalftone = 0x0413, // version, length and data |
79 | | IDsBaseValue = 0x0414, // base value for new layers ID's |
80 | | UnicodeAlphaNames = 0x0415, // length plus Unicode string |
81 | | IndexedColourTableCount = 0x0416, // [Photoshop 6.0 and later] 2 bytes |
82 | | TransparentIndex = 0x0417, // [Photoshop 6.0 and later] 2 bytes |
83 | | GlobalAltitude = 0x0419, // [Photoshop 6.0 and later] 4 bytes |
84 | | Slices = 0x041a, // [Photoshop 6.0 and later] see additional Adobe info |
85 | | WorkflowURL = 0x041b, // [Photoshop 6.0 and later] 4 bytes length + Unicode string |
86 | | JumpToXPEP = 0x041c, // [Photoshop 6.0 and later] see additional Adobe info |
87 | | AlphaIdentifiers = 0x041d, // [Photoshop 6.0 and later] 4*(n+1) bytes |
88 | | URLList = 0x041e, // [Photoshop 6.0 and later] structured Unicode URL's |
89 | | VersionInfo = 0x0421, // [Photoshop 6.0 and later] see additional Adobe info |
90 | | ExifInfo = 0x0422, // [Photoshop 7.0?] Exif metadata |
91 | | XMPPacket = 0x0424, // [Photoshop 7.0?] XMP packet -- see http://www.adobe.com/devnet/xmp/pdfs/xmp_specification.pdf |
92 | | ClippingPathName = 0x0bb7, // [Photoshop 6.0 and later] name of clipping path |
93 | | MorePrintFlags = 0x2710, // [Photoshop 6.0 and later] Print flags information. 2 bytes version (=1), 1 byte center |
94 | | // crop marks, 1 byte (=0), 4 bytes bleed width value, 2 bytes bleed width scale. |
95 | | }; |
96 | | |
97 | | // ***************************************************************************** |
98 | | // class member definitions |
99 | | namespace Exiv2 { |
100 | 83 | PsdImage::PsdImage(BasicIo::UniquePtr io) : Image(ImageType::psd, mdExif | mdIptc | mdXmp, std::move(io)) { |
101 | 83 | } // PsdImage::PsdImage |
102 | | |
103 | 40 | std::string PsdImage::mimeType() const { |
104 | 40 | return "image/x-photoshop"; |
105 | 40 | } |
106 | | |
107 | 0 | void PsdImage::setComment(const std::string&) { |
108 | | // not supported |
109 | 0 | throw(Error(ErrorCode::kerInvalidSettingForImage, "Image comment", "Photoshop")); |
110 | 0 | } |
111 | | |
112 | 76 | void PsdImage::readMetadata() { |
113 | | #ifdef EXIV2_DEBUG_MESSAGES |
114 | | std::cerr << "Exiv2::PsdImage::readMetadata: Reading Photoshop file " << io_->path() << "\n"; |
115 | | #endif |
116 | 76 | if (io_->open() != 0) { |
117 | 0 | throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError()); |
118 | 0 | } |
119 | 76 | IoCloser closer(*io_); |
120 | | // Ensure that this is the correct image type |
121 | 76 | if (!isPsdType(*io_, false)) { |
122 | 0 | if (io_->error() || io_->eof()) |
123 | 0 | throw Error(ErrorCode::kerFailedToReadImageData); |
124 | 0 | throw Error(ErrorCode::kerNotAnImage, "Photoshop"); |
125 | 0 | } |
126 | 76 | clearMetadata(); |
127 | | |
128 | | /* |
129 | | The Photoshop header goes as follows -- all numbers are in big-endian byte order: |
130 | | |
131 | | offset length name description |
132 | | ====== ======= ========= ========= |
133 | | 0 4 bytes signature always '8BPS' |
134 | | 4 2 bytes version always equal to 1 |
135 | | 6 6 bytes reserved must be zero |
136 | | 12 2 bytes channels number of channels in the image, including alpha channels (1 to 24) |
137 | | 14 4 bytes rows the height of the image in pixels |
138 | | 18 4 bytes columns the width of the image in pixels |
139 | | 22 2 bytes depth the number of bits per channel |
140 | | 24 2 bytes mode the color mode of the file; Supported values are: Bitmap=0; Grayscale=1; |
141 | | Indexed=2; RGB=3; CMYK=4; Multichannel=7; Duotone=8; Lab=9 |
142 | | */ |
143 | 76 | byte buf[26]; |
144 | 76 | if (io_->read(buf, 26) != 26) { |
145 | 0 | throw Error(ErrorCode::kerNotAnImage, "Photoshop"); |
146 | 0 | } |
147 | 76 | pixelWidth_ = getLong(buf + 18, bigEndian); |
148 | 76 | pixelHeight_ = getLong(buf + 14, bigEndian); |
149 | | |
150 | | // immediately following the image header is the color mode data section, |
151 | | // the first four bytes of which specify the byte size of the whole section |
152 | 76 | if (io_->read(buf, 4) != 4) { |
153 | 0 | throw Error(ErrorCode::kerNotAnImage, "Photoshop"); |
154 | 0 | } |
155 | | |
156 | | // skip it |
157 | 76 | if (io_->seek(getULong(buf, bigEndian), BasicIo::cur)) { |
158 | 0 | throw Error(ErrorCode::kerNotAnImage, "Photoshop"); |
159 | 0 | } |
160 | | |
161 | | // after the color data section, comes a list of resource blocks, preceded by the total byte size |
162 | 76 | if (io_->read(buf, 4) != 4) { |
163 | 10 | throw Error(ErrorCode::kerNotAnImage, "Photoshop"); |
164 | 10 | } |
165 | 66 | uint32_t resourcesLength = getULong(buf, bigEndian); |
166 | 66 | Internal::enforce(resourcesLength < io_->size(), Exiv2::ErrorCode::kerCorruptedMetadata); |
167 | | |
168 | 78 | while (resourcesLength > 0) { |
169 | 31 | Internal::enforce(resourcesLength >= 8, Exiv2::ErrorCode::kerCorruptedMetadata); |
170 | 31 | resourcesLength -= 8; |
171 | 31 | if (io_->read(buf, 8) != 8) { |
172 | 0 | throw Error(ErrorCode::kerNotAnImage, "Photoshop"); |
173 | 0 | } |
174 | | |
175 | 31 | if (!Photoshop::isIrb(buf)) { |
176 | 19 | break; // bad resource type |
177 | 19 | } |
178 | 12 | uint16_t resourceId = getUShort(buf + 4, bigEndian); |
179 | 12 | uint32_t resourceNameLength = buf[6] & ~1; |
180 | | |
181 | | // skip the resource name, plus any padding |
182 | 12 | Internal::enforce(resourceNameLength <= resourcesLength, Exiv2::ErrorCode::kerCorruptedMetadata); |
183 | 12 | resourcesLength -= resourceNameLength; |
184 | 12 | io_->seek(resourceNameLength, BasicIo::cur); |
185 | | |
186 | | // read resource size |
187 | 12 | Internal::enforce(resourcesLength >= 4, Exiv2::ErrorCode::kerCorruptedMetadata); |
188 | 12 | resourcesLength -= 4; |
189 | 12 | if (io_->read(buf, 4) != 4) { |
190 | 0 | throw Error(ErrorCode::kerNotAnImage, "Photoshop"); |
191 | 0 | } |
192 | 12 | uint32_t resourceSize = getULong(buf, bigEndian); |
193 | 12 | const size_t curOffset = io_->tell(); |
194 | | |
195 | | #ifdef EXIV2_DEBUG_MESSAGES |
196 | | std::cerr << std::hex << "resourceId: " << resourceId << std::dec << " length: " << resourceSize << std::hex |
197 | | << "\n"; |
198 | | #endif |
199 | | |
200 | 12 | Internal::enforce(resourceSize <= resourcesLength, Exiv2::ErrorCode::kerCorruptedMetadata); |
201 | 12 | readResourceBlock(resourceId, resourceSize); |
202 | 12 | resourceSize = (resourceSize + 1) & ~1; // pad to even |
203 | 12 | Internal::enforce(resourceSize <= resourcesLength, Exiv2::ErrorCode::kerCorruptedMetadata); |
204 | 12 | resourcesLength -= resourceSize; |
205 | 12 | io_->seek(curOffset + resourceSize, BasicIo::beg); |
206 | 12 | } |
207 | | |
208 | 66 | } // PsdImage::readMetadata |
209 | | |
210 | 0 | void PsdImage::readResourceBlock(uint16_t resourceId, uint32_t resourceSize) { |
211 | 0 | switch (resourceId) { |
212 | 0 | case kPhotoshopResourceID::IPTC_NAA: { |
213 | 0 | DataBuf rawIPTC(resourceSize); |
214 | 0 | io_->read(rawIPTC.data(), rawIPTC.size()); |
215 | 0 | if (io_->error() || io_->eof()) |
216 | 0 | throw Error(ErrorCode::kerFailedToReadImageData); |
217 | 0 | if (IptcParser::decode(iptcData_, rawIPTC.c_data(), rawIPTC.size())) { |
218 | 0 | #ifndef SUPPRESS_WARNINGS |
219 | 0 | EXV_WARNING << "Failed to decode IPTC metadata.\n"; |
220 | 0 | #endif |
221 | 0 | iptcData_.clear(); |
222 | 0 | } |
223 | 0 | break; |
224 | 0 | } |
225 | | |
226 | 0 | case kPhotoshopResourceID::ExifInfo: { |
227 | 0 | DataBuf rawExif(resourceSize); |
228 | 0 | io_->read(rawExif.data(), rawExif.size()); |
229 | 0 | if (io_->error() || io_->eof()) |
230 | 0 | throw Error(ErrorCode::kerFailedToReadImageData); |
231 | 0 | ByteOrder bo = ExifParser::decode(exifData_, rawExif.c_data(), rawExif.size()); |
232 | 0 | setByteOrder(bo); |
233 | 0 | if (!rawExif.empty() && byteOrder() == invalidByteOrder) { |
234 | 0 | #ifndef SUPPRESS_WARNINGS |
235 | 0 | EXV_WARNING << "Failed to decode Exif metadata.\n"; |
236 | 0 | #endif |
237 | 0 | exifData_.clear(); |
238 | 0 | } |
239 | 0 | break; |
240 | 0 | } |
241 | | |
242 | 0 | case kPhotoshopResourceID::XMPPacket: { |
243 | 0 | DataBuf xmpPacket(resourceSize); |
244 | 0 | io_->read(xmpPacket.data(), xmpPacket.size()); |
245 | 0 | if (io_->error() || io_->eof()) |
246 | 0 | throw Error(ErrorCode::kerFailedToReadImageData); |
247 | 0 | xmpPacket_.assign(xmpPacket.c_str(), xmpPacket.size()); |
248 | 0 | if (!xmpPacket_.empty() && XmpParser::decode(xmpData_, xmpPacket_)) { |
249 | 0 | #ifndef SUPPRESS_WARNINGS |
250 | 0 | EXV_WARNING << "Failed to decode XMP metadata.\n"; |
251 | 0 | #endif |
252 | 0 | } |
253 | 0 | break; |
254 | 0 | } |
255 | | |
256 | | // - PS 4.0 preview data is fetched from ThumbnailResource |
257 | | // - PS >= 5.0 preview data is fetched from ThumbnailResource2 |
258 | 0 | case kPhotoshopResourceID::ThumbnailResource: |
259 | 0 | case kPhotoshopResourceID::ThumbnailResource2: { |
260 | | /* |
261 | | Photoshop thumbnail resource header |
262 | | |
263 | | offset length name description |
264 | | ====== ======== ==== =========== |
265 | | 0 4 bytes format = 1 (kJpegRGB). Also supports kRawRGB (0). |
266 | | 4 4 bytes width Width of thumbnail in pixels. |
267 | | 8 4 bytes height Height of thumbnail in pixels. |
268 | | 12 4 bytes widthbytes Padded row bytes as (width * bitspixel + 31) / 32 * 4. |
269 | | 16 4 bytes size Total size as widthbytes * height * planes |
270 | | 20 4 bytes compressedsize Size after compression. Used for consistency check. |
271 | | 24 2 bytes bitspixel = 24. Bits per pixel. |
272 | | 26 2 bytes planes = 1. Number of planes. |
273 | | 28 variable data JFIF data in RGB format. |
274 | | Note: For resource ID 1033 the data is in BGR format. |
275 | | */ |
276 | 0 | byte buf[28]; |
277 | 0 | if (io_->read(buf, 28) != 28) { |
278 | 0 | throw Error(ErrorCode::kerNotAnImage, "Photoshop"); |
279 | 0 | } |
280 | 0 | NativePreview nativePreview; |
281 | 0 | nativePreview.position_ = io_->tell(); |
282 | 0 | nativePreview.size_ = getLong(buf + 20, bigEndian); // compressedsize |
283 | 0 | nativePreview.width_ = getLong(buf + 4, bigEndian); |
284 | 0 | nativePreview.height_ = getLong(buf + 8, bigEndian); |
285 | 0 | const uint32_t format = getLong(buf + 0, bigEndian); |
286 | |
|
287 | 0 | if (nativePreview.size_ > 0 && nativePreview.position_ > 0) { |
288 | 0 | io_->seek(static_cast<long>(nativePreview.size_), BasicIo::cur); |
289 | 0 | if (io_->error() || io_->eof()) |
290 | 0 | throw Error(ErrorCode::kerFailedToReadImageData); |
291 | | |
292 | | // unsupported format of native preview |
293 | 0 | if (format != 1) |
294 | 0 | break; |
295 | 0 | nativePreview.filter_ = ""; |
296 | 0 | nativePreview.mimeType_ = "image/jpeg"; |
297 | 0 | nativePreviews_.push_back(std::move(nativePreview)); |
298 | 0 | } |
299 | 0 | break; |
300 | 0 | } |
301 | | |
302 | 0 | default: |
303 | 0 | break; |
304 | 0 | } |
305 | 0 | } // PsdImage::readResourceBlock |
306 | | |
307 | 0 | void PsdImage::writeMetadata() { |
308 | 0 | if (io_->open() != 0) { |
309 | 0 | throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError()); |
310 | 0 | } |
311 | 0 | IoCloser closer(*io_); |
312 | 0 | MemIo tempIo; |
313 | |
|
314 | 0 | doWriteMetadata(tempIo); // may throw |
315 | 0 | io_->close(); |
316 | 0 | io_->transfer(tempIo); // may throw |
317 | |
|
318 | 0 | } // PsdImage::writeMetadata |
319 | | |
320 | 0 | void PsdImage::doWriteMetadata(BasicIo& outIo) { |
321 | 0 | if (!io_->isopen()) |
322 | 0 | throw Error(ErrorCode::kerInputDataReadFailed); |
323 | 0 | if (!outIo.isopen()) |
324 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
325 | | |
326 | | #ifdef EXIV2_DEBUG_MESSAGES |
327 | | std::cout << "Exiv2::PsdImage::doWriteMetadata: Writing PSD file " << io_->path() << "\n"; |
328 | | std::cout << "Exiv2::PsdImage::doWriteMetadata: tmp file created " << outIo.path() << "\n"; |
329 | | #endif |
330 | | |
331 | | // Ensure that this is the correct image type |
332 | 0 | if (!isPsdType(*io_, true)) { |
333 | 0 | if (io_->error() || io_->eof()) |
334 | 0 | throw Error(ErrorCode::kerInputDataReadFailed); |
335 | 0 | throw Error(ErrorCode::kerNoImageInInputData); |
336 | 0 | } |
337 | | |
338 | 0 | io_->seek(0, BasicIo::beg); // rewind |
339 | |
|
340 | 0 | DataBuf lbuf(4096); |
341 | 0 | byte buf[8]; |
342 | | |
343 | | // Get Photoshop header from original file |
344 | 0 | byte psd_head[26]; |
345 | 0 | if (io_->read(psd_head, 26) != 26) |
346 | 0 | throw Error(ErrorCode::kerNotAnImage, "Photoshop"); |
347 | | |
348 | | // Write Photoshop header data out to new PSD file |
349 | 0 | if (outIo.write(psd_head, 26) != 26) |
350 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
351 | | |
352 | | // Read colorDataLength from original PSD |
353 | 0 | if (io_->read(buf, 4) != 4) |
354 | 0 | throw Error(ErrorCode::kerNotAnImage, "Photoshop"); |
355 | | |
356 | 0 | uint32_t colorDataLength = getULong(buf, bigEndian); |
357 | | |
358 | | // Write colorDataLength |
359 | 0 | ul2Data(buf, colorDataLength, bigEndian); |
360 | 0 | if (outIo.write(buf, 4) != 4) |
361 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
362 | | #ifdef EXIV2_DEBUG_MESSAGES |
363 | | std::cerr << std::dec << "colorDataLength: " << colorDataLength << "\n"; |
364 | | #endif |
365 | | // Copy colorData |
366 | 0 | size_t readTotal = 0; |
367 | 0 | while (readTotal < colorDataLength) { |
368 | 0 | auto toRead = std::min<size_t>(colorDataLength - readTotal, lbuf.size()); |
369 | 0 | if (io_->read(lbuf.data(), toRead) != toRead) |
370 | 0 | throw Error(ErrorCode::kerNotAnImage, "Photoshop"); |
371 | 0 | readTotal += toRead; |
372 | 0 | if (outIo.write(lbuf.c_data(), toRead) != toRead) |
373 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
374 | 0 | } |
375 | 0 | if (outIo.error()) |
376 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
377 | | |
378 | 0 | const size_t resLenOffset = io_->tell(); // remember for later update |
379 | | |
380 | | // Read length of all resource blocks from original PSD |
381 | 0 | if (io_->read(buf, 4) != 4) |
382 | 0 | throw Error(ErrorCode::kerNotAnImage, "Photoshop"); |
383 | | |
384 | 0 | uint32_t oldResLength = getULong(buf, bigEndian); |
385 | 0 | uint32_t newResLength = 0; |
386 | | |
387 | | // Write oldResLength (will be updated later) |
388 | 0 | ul2Data(buf, oldResLength, bigEndian); |
389 | 0 | if (outIo.write(buf, 4) != 4) |
390 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
391 | | |
392 | | #ifdef EXIV2_DEBUG_MESSAGES |
393 | | std::cerr << std::dec << "oldResLength: " << oldResLength << "\n"; |
394 | | #endif |
395 | | |
396 | | // Iterate over original resource blocks. |
397 | | // Replace or insert IPTC, EXIF and XMP |
398 | | // Original resource blocks assumed to be sorted ASC |
399 | | |
400 | 0 | bool iptcDone = false; |
401 | 0 | bool exifDone = false; |
402 | 0 | bool xmpDone = false; |
403 | 0 | while (oldResLength > 0) { |
404 | 0 | if (io_->read(buf, 8) != 8) |
405 | 0 | throw Error(ErrorCode::kerNotAnImage, "Photoshop"); |
406 | | |
407 | | // read resource type and ID |
408 | 0 | uint32_t resourceType = getULong(buf, bigEndian); |
409 | |
|
410 | 0 | if (!Photoshop::isIrb(buf)) { |
411 | 0 | throw Error(ErrorCode::kerNotAnImage, "Photoshop"); // bad resource type |
412 | 0 | } |
413 | 0 | uint16_t resourceId = getUShort(buf + 4, bigEndian); |
414 | 0 | uint32_t resourceNameLength = buf[6]; |
415 | 0 | uint32_t adjResourceNameLen = resourceNameLength & ~1; |
416 | 0 | unsigned char resourceNameFirstChar = buf[7]; |
417 | | |
418 | | // read rest of resource name, plus any padding |
419 | 0 | DataBuf resName(256); |
420 | 0 | if (io_->read(resName.data(), adjResourceNameLen) != adjResourceNameLen) |
421 | 0 | throw Error(ErrorCode::kerNotAnImage, "Photoshop"); |
422 | | |
423 | | // read resource size (actual length w/o padding!) |
424 | 0 | if (io_->read(buf, 4) != 4) |
425 | 0 | throw Error(ErrorCode::kerNotAnImage, "Photoshop"); |
426 | | |
427 | 0 | uint32_t resourceSize = getULong(buf, bigEndian); |
428 | 0 | uint32_t pResourceSize = (resourceSize + 1) & ~1; // padded resource size |
429 | 0 | const size_t curOffset = io_->tell(); |
430 | | |
431 | | // Write IPTC_NAA resource block |
432 | 0 | if (resourceId >= kPhotoshopResourceID::IPTC_NAA && !iptcDone) { |
433 | 0 | newResLength += writeIptcData(iptcData_, outIo); |
434 | 0 | iptcDone = true; |
435 | 0 | } |
436 | | |
437 | | // Write ExifInfo resource block |
438 | 0 | else if (resourceId >= kPhotoshopResourceID::ExifInfo && !exifDone) { |
439 | 0 | newResLength += writeExifData(exifData_, outIo); |
440 | 0 | exifDone = true; |
441 | 0 | } |
442 | | |
443 | | // Write XMPpacket resource block |
444 | 0 | else if (resourceId >= kPhotoshopResourceID::XMPPacket && !xmpDone) { |
445 | 0 | newResLength += writeXmpData(xmpData_, outIo); |
446 | 0 | xmpDone = true; |
447 | 0 | } |
448 | | |
449 | | // Copy all other resource blocks |
450 | 0 | if (resourceId != kPhotoshopResourceID::IPTC_NAA && resourceId != kPhotoshopResourceID::ExifInfo && |
451 | 0 | resourceId != kPhotoshopResourceID::XMPPacket) { |
452 | | #ifdef EXIV2_DEBUG_MESSAGES |
453 | | std::cerr << std::hex << "copy : resourceType: " << resourceType << "\n"; |
454 | | std::cerr << std::hex << "copy : resourceId: " << resourceId << "\n"; |
455 | | std::cerr << std::dec; |
456 | | #endif |
457 | | // Copy resource block to new PSD file |
458 | 0 | ul2Data(buf, resourceType, bigEndian); |
459 | 0 | if (outIo.write(buf, 4) != 4) |
460 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
461 | 0 | us2Data(buf, resourceId, bigEndian); |
462 | 0 | if (outIo.write(buf, 2) != 2) |
463 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
464 | | // Write resource name as Pascal string |
465 | 0 | buf[0] = resourceNameLength & 0x00ff; |
466 | 0 | if (outIo.write(buf, 1) != 1) |
467 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
468 | 0 | buf[0] = resourceNameFirstChar; |
469 | 0 | if (outIo.write(buf, 1) != 1) |
470 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
471 | 0 | if (outIo.write(resName.c_data(), adjResourceNameLen) != static_cast<size_t>(adjResourceNameLen)) |
472 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
473 | 0 | ul2Data(buf, resourceSize, bigEndian); |
474 | 0 | if (outIo.write(buf, 4) != 4) |
475 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
476 | | |
477 | 0 | readTotal = 0; |
478 | 0 | while (readTotal < pResourceSize) { |
479 | | /// \todo almost same code as in lines 403-410. Factor out & reuse! |
480 | 0 | auto toRead = std::min<size_t>(pResourceSize - readTotal, lbuf.size()); |
481 | 0 | if (io_->read(lbuf.data(), toRead) != toRead) { |
482 | 0 | throw Error(ErrorCode::kerNotAnImage, "Photoshop"); |
483 | 0 | } |
484 | 0 | readTotal += toRead; |
485 | 0 | if (outIo.write(lbuf.c_data(), toRead) != toRead) |
486 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
487 | 0 | } |
488 | 0 | if (outIo.error()) |
489 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
490 | 0 | newResLength += pResourceSize + adjResourceNameLen + 12; |
491 | 0 | } |
492 | | |
493 | 0 | io_->seek(curOffset + pResourceSize, BasicIo::beg); |
494 | 0 | oldResLength -= (12 + adjResourceNameLen + pResourceSize); |
495 | 0 | } |
496 | | |
497 | | // Append IPTC_NAA resource block, if not yet written |
498 | 0 | if (!iptcDone) { |
499 | 0 | newResLength += writeIptcData(iptcData_, outIo); |
500 | 0 | iptcDone = true; |
501 | 0 | } |
502 | | |
503 | | // Append ExifInfo resource block, if not yet written |
504 | 0 | if (!exifDone) { |
505 | 0 | newResLength += writeExifData(exifData_, outIo); |
506 | 0 | } |
507 | | |
508 | | // Append XmpPacket resource block, if not yet written |
509 | 0 | if (!xmpDone) { |
510 | 0 | newResLength += writeXmpData(xmpData_, outIo); |
511 | 0 | } |
512 | | |
513 | | // Populate the fake data, only make sense for remoteio, httpio and sshio. |
514 | | // it avoids allocating memory for parts of the file that contain image-date. |
515 | 0 | io_->populateFakeData(); |
516 | | |
517 | | // Copy remaining data |
518 | 0 | size_t readSize = io_->read(lbuf.data(), lbuf.size()); |
519 | 0 | while (readSize != 0) { |
520 | 0 | if (outIo.write(lbuf.c_data(), readSize) != readSize) |
521 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
522 | 0 | readSize = io_->read(lbuf.data(), lbuf.size()); |
523 | 0 | } |
524 | 0 | if (outIo.error()) |
525 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
526 | | |
527 | | // Update length of resources |
528 | | #ifdef EXIV2_DEBUG_MESSAGES |
529 | | std::cerr << "newResLength: " << newResLength << "\n"; |
530 | | #endif |
531 | 0 | outIo.seek(resLenOffset, BasicIo::beg); |
532 | 0 | ul2Data(buf, newResLength, bigEndian); |
533 | 0 | if (outIo.write(buf, 4) != 4) |
534 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
535 | |
|
536 | 0 | } // PsdImage::doWriteMetadata |
537 | | |
538 | 0 | uint32_t PsdImage::writeIptcData(const IptcData& iptcData, BasicIo& out) { |
539 | 0 | uint32_t resLength = 0; |
540 | 0 | byte buf[8]; |
541 | |
|
542 | 0 | if (!iptcData.empty()) { |
543 | 0 | DataBuf rawIptc = IptcParser::encode(iptcData); |
544 | 0 | if (!rawIptc.empty()) { |
545 | | #ifdef EXIV2_DEBUG_MESSAGES |
546 | | std::cerr << std::hex << "write: resourceId: " << kPhotoshopResourceID::IPTC_NAA << "\n"; |
547 | | std::cerr << std::dec << "Writing IPTC_NAA: size: " << rawIptc.size() << "\n"; |
548 | | #endif |
549 | 0 | if (out.write(reinterpret_cast<const byte*>(Photoshop::irbId_.front()), 4) != 4) |
550 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
551 | 0 | us2Data(buf, kPhotoshopResourceID::IPTC_NAA, bigEndian); |
552 | 0 | if (out.write(buf, 2) != 2) |
553 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
554 | 0 | us2Data(buf, 0, bigEndian); // NULL resource name |
555 | 0 | if (out.write(buf, 2) != 2) |
556 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
557 | 0 | ul2Data(buf, static_cast<uint32_t>(rawIptc.size()), bigEndian); |
558 | 0 | if (out.write(buf, 4) != 4) |
559 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
560 | | // Write encoded Iptc data |
561 | 0 | if (out.write(rawIptc.c_data(), rawIptc.size()) != rawIptc.size()) |
562 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
563 | 0 | resLength += static_cast<uint32_t>(rawIptc.size()) + 12; |
564 | 0 | if (rawIptc.size() & 1) // even padding |
565 | 0 | { |
566 | 0 | buf[0] = 0; |
567 | 0 | if (out.write(buf, 1) != 1) |
568 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
569 | 0 | resLength++; |
570 | 0 | } |
571 | 0 | } |
572 | 0 | } |
573 | 0 | return resLength; |
574 | 0 | } // PsdImage::writeIptcData |
575 | | |
576 | 0 | uint32_t PsdImage::writeExifData(ExifData& exifData, BasicIo& out) { |
577 | 0 | uint32_t resLength = 0; |
578 | 0 | byte buf[8]; |
579 | |
|
580 | 0 | if (!exifData.empty()) { |
581 | 0 | Blob blob; |
582 | 0 | ByteOrder bo = byteOrder(); |
583 | 0 | if (bo == invalidByteOrder) { |
584 | 0 | bo = littleEndian; |
585 | 0 | setByteOrder(bo); |
586 | 0 | } |
587 | 0 | ExifParser::encode(blob, bo, exifData); |
588 | |
|
589 | 0 | if (!blob.empty()) { |
590 | | #ifdef EXIV2_DEBUG_MESSAGES |
591 | | std::cerr << std::hex << "write: resourceId: " << kPhotoshopResourceID::ExifInfo << "\n"; |
592 | | std::cerr << std::dec << "Writing ExifInfo: size: " << blob.size() << "\n"; |
593 | | #endif |
594 | 0 | if (out.write(reinterpret_cast<const byte*>(Photoshop::irbId_.front()), 4) != 4) |
595 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
596 | 0 | us2Data(buf, kPhotoshopResourceID::ExifInfo, bigEndian); |
597 | 0 | if (out.write(buf, 2) != 2) |
598 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
599 | 0 | us2Data(buf, 0, bigEndian); // NULL resource name |
600 | 0 | if (out.write(buf, 2) != 2) |
601 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
602 | 0 | ul2Data(buf, static_cast<uint32_t>(blob.size()), bigEndian); |
603 | 0 | if (out.write(buf, 4) != 4) |
604 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
605 | | // Write encoded Exif data |
606 | 0 | if (out.write(blob.data(), blob.size()) != blob.size()) |
607 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
608 | 0 | resLength += static_cast<long>(blob.size()) + 12; |
609 | 0 | if (blob.size() & 1) // even padding |
610 | 0 | { |
611 | 0 | buf[0] = 0; |
612 | 0 | if (out.write(buf, 1) != 1) |
613 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
614 | 0 | resLength++; |
615 | 0 | } |
616 | 0 | } |
617 | 0 | } |
618 | 0 | return resLength; |
619 | 0 | } // PsdImage::writeExifData |
620 | | |
621 | 0 | uint32_t PsdImage::writeXmpData(const XmpData& xmpData, BasicIo& out) const { |
622 | 0 | std::string xmpPacket; |
623 | 0 | uint32_t resLength = 0; |
624 | 0 | byte buf[8]; |
625 | |
|
626 | | #ifdef EXIV2_DEBUG_MESSAGES |
627 | | std::cerr << "writeXmpFromPacket(): " << writeXmpFromPacket() << "\n"; |
628 | | #endif |
629 | | // writeXmpFromPacket(true); |
630 | 0 | if (!writeXmpFromPacket() && XmpParser::encode(xmpPacket, xmpData) > 1) { |
631 | 0 | #ifndef SUPPRESS_WARNINGS |
632 | 0 | EXV_ERROR << "Failed to encode XMP metadata.\n"; |
633 | 0 | #endif |
634 | 0 | } |
635 | |
|
636 | 0 | if (!xmpPacket.empty()) { |
637 | | #ifdef EXIV2_DEBUG_MESSAGES |
638 | | std::cerr << std::hex << "write: resourceId: " << kPhotoshopResourceID::XMPPacket << "\n"; |
639 | | std::cerr << std::dec << "Writing XMPPacket: size: " << xmpPacket.size() << "\n"; |
640 | | #endif |
641 | 0 | if (out.write(reinterpret_cast<const byte*>(Photoshop::irbId_.front()), 4) != 4) |
642 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
643 | 0 | us2Data(buf, kPhotoshopResourceID::XMPPacket, bigEndian); |
644 | 0 | if (out.write(buf, 2) != 2) |
645 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
646 | 0 | us2Data(buf, 0, bigEndian); // NULL resource name |
647 | 0 | if (out.write(buf, 2) != 2) |
648 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
649 | 0 | ul2Data(buf, static_cast<uint32_t>(xmpPacket.size()), bigEndian); |
650 | 0 | if (out.write(buf, 4) != 4) |
651 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
652 | | // Write XMPPacket |
653 | 0 | if (out.write(reinterpret_cast<const byte*>(xmpPacket.data()), xmpPacket.size()) != xmpPacket.size()) |
654 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
655 | 0 | if (out.error()) |
656 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
657 | 0 | resLength += static_cast<uint32_t>(xmpPacket.size()) + 12; |
658 | 0 | if (xmpPacket.size() & 1) // even padding |
659 | 0 | { |
660 | 0 | buf[0] = 0; |
661 | 0 | if (out.write(buf, 1) != 1) |
662 | 0 | throw Error(ErrorCode::kerImageWriteFailed); |
663 | 0 | resLength++; |
664 | 0 | } |
665 | 0 | } |
666 | 0 | return resLength; |
667 | 0 | } // PsdImage::writeXmpData |
668 | | |
669 | | // ************************************************************************* |
670 | | // free functions |
671 | 83 | Image::UniquePtr newPsdInstance(BasicIo::UniquePtr io, bool /*create*/) { |
672 | 83 | auto image = std::make_unique<PsdImage>(std::move(io)); |
673 | 83 | if (!image->good()) { |
674 | 7 | return nullptr; |
675 | 7 | } |
676 | 76 | return image; |
677 | 83 | } |
678 | | |
679 | 3.50k | bool isPsdType(BasicIo& iIo, bool advance) { |
680 | 3.50k | const int32_t len = 6; |
681 | 3.50k | const std::array<byte, len> PsdHeader{'8', 'B', 'P', 'S', 0, 1}; |
682 | 3.50k | std::array<byte, len> buf; |
683 | 3.50k | iIo.read(buf.data(), len); |
684 | 3.50k | if (iIo.error() || iIo.eof()) { |
685 | 287 | return false; |
686 | 287 | } |
687 | 3.21k | bool matched = buf == PsdHeader; |
688 | 3.21k | if (!advance || !matched) { |
689 | 3.21k | iIo.seek(-len, BasicIo::cur); |
690 | 3.21k | } |
691 | | |
692 | 3.21k | return matched; |
693 | 3.50k | } |
694 | | } // namespace Exiv2 |