Coverage Report

Created: 2026-02-26 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
655
PsdImage::PsdImage(BasicIo::UniquePtr io) : Image(ImageType::psd, mdExif | mdIptc | mdXmp, std::move(io)) {
101
655
}  // PsdImage::PsdImage
102
103
0
std::string PsdImage::mimeType() const {
104
0
  return "image/x-photoshop";
105
0
}
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
648
void PsdImage::readMetadata() {
113
#ifdef EXIV2_DEBUG_MESSAGES
114
  std::cerr << "Exiv2::PsdImage::readMetadata: Reading Photoshop file " << io_->path() << "\n";
115
#endif
116
648
  if (io_->open() != 0) {
117
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
118
0
  }
119
648
  IoCloser closer(*io_);
120
  // Ensure that this is the correct image type
121
648
  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
648
  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
648
  byte buf[26];
144
648
  if (io_->read(buf, 26) != 26) {
145
0
    throw Error(ErrorCode::kerNotAnImage, "Photoshop");
146
0
  }
147
648
  pixelWidth_ = getLong(buf + 18, bigEndian);
148
648
  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
648
  if (io_->read(buf, 4) != 4) {
153
0
    throw Error(ErrorCode::kerNotAnImage, "Photoshop");
154
0
  }
155
156
  // skip it
157
648
  if (io_->seek(getULong(buf, bigEndian), BasicIo::cur)) {
158
42
    throw Error(ErrorCode::kerNotAnImage, "Photoshop");
159
42
  }
160
161
  // after the color data section, comes a list of resource blocks, preceded by the total byte size
162
606
  if (io_->read(buf, 4) != 4) {
163
13
    throw Error(ErrorCode::kerNotAnImage, "Photoshop");
164
13
  }
165
593
  uint32_t resourcesLength = getULong(buf, bigEndian);
166
593
  Internal::enforce(resourcesLength < io_->size(), Exiv2::ErrorCode::kerCorruptedMetadata);
167
168
1.52k
  while (resourcesLength > 0) {
169
1.16k
    Internal::enforce(resourcesLength >= 8, Exiv2::ErrorCode::kerCorruptedMetadata);
170
1.16k
    resourcesLength -= 8;
171
1.16k
    if (io_->read(buf, 8) != 8) {
172
34
      throw Error(ErrorCode::kerNotAnImage, "Photoshop");
173
34
    }
174
175
1.12k
    if (!Photoshop::isIrb(buf)) {
176
187
      break;  // bad resource type
177
187
    }
178
942
    uint16_t resourceId = getUShort(buf + 4, bigEndian);
179
942
    uint32_t resourceNameLength = buf[6] & ~1;
180
181
    // skip the resource name, plus any padding
182
942
    Internal::enforce(resourceNameLength <= resourcesLength, Exiv2::ErrorCode::kerCorruptedMetadata);
183
942
    resourcesLength -= resourceNameLength;
184
942
    io_->seek(resourceNameLength, BasicIo::cur);
185
186
    // read resource size
187
942
    Internal::enforce(resourcesLength >= 4, Exiv2::ErrorCode::kerCorruptedMetadata);
188
942
    resourcesLength -= 4;
189
942
    if (io_->read(buf, 4) != 4) {
190
9
      throw Error(ErrorCode::kerNotAnImage, "Photoshop");
191
9
    }
192
933
    uint32_t resourceSize = getULong(buf, bigEndian);
193
933
    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
933
    Internal::enforce(resourceSize <= resourcesLength, Exiv2::ErrorCode::kerCorruptedMetadata);
201
933
    readResourceBlock(resourceId, resourceSize);
202
933
    resourceSize = (resourceSize + 1) & ~1;  // pad to even
203
933
    Internal::enforce(resourceSize <= resourcesLength, Exiv2::ErrorCode::kerCorruptedMetadata);
204
933
    resourcesLength -= resourceSize;
205
933
    io_->seek(curOffset + resourceSize, BasicIo::beg);
206
933
  }
207
208
593
}  // PsdImage::readMetadata
209
210
782
void PsdImage::readResourceBlock(uint16_t resourceId, uint32_t resourceSize) {
211
782
  switch (resourceId) {
212
130
    case kPhotoshopResourceID::IPTC_NAA: {
213
130
      DataBuf rawIPTC(resourceSize);
214
130
      io_->read(rawIPTC.data(), rawIPTC.size());
215
130
      if (io_->error() || io_->eof())
216
7
        throw Error(ErrorCode::kerFailedToReadImageData);
217
123
      if (IptcParser::decode(iptcData_, rawIPTC.c_data(), rawIPTC.size())) {
218
44
#ifndef SUPPRESS_WARNINGS
219
44
        EXV_WARNING << "Failed to decode IPTC metadata.\n";
220
44
#endif
221
44
        iptcData_.clear();
222
44
      }
223
123
      break;
224
130
    }
225
226
43
    case kPhotoshopResourceID::ExifInfo: {
227
43
      DataBuf rawExif(resourceSize);
228
43
      io_->read(rawExif.data(), rawExif.size());
229
43
      if (io_->error() || io_->eof())
230
7
        throw Error(ErrorCode::kerFailedToReadImageData);
231
36
      ByteOrder bo = ExifParser::decode(exifData_, rawExif.c_data(), rawExif.size());
232
36
      setByteOrder(bo);
233
36
      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
36
      break;
240
43
    }
241
242
50
    case kPhotoshopResourceID::XMPPacket: {
243
50
      DataBuf xmpPacket(resourceSize);
244
50
      io_->read(xmpPacket.data(), xmpPacket.size());
245
50
      if (io_->error() || io_->eof())
246
7
        throw Error(ErrorCode::kerFailedToReadImageData);
247
43
      xmpPacket_.assign(xmpPacket.c_str(), xmpPacket.size());
248
43
      if (!xmpPacket_.empty() && XmpParser::decode(xmpData_, xmpPacket_)) {
249
25
#ifndef SUPPRESS_WARNINGS
250
25
        EXV_WARNING << "Failed to decode XMP metadata.\n";
251
25
#endif
252
25
      }
253
43
      break;
254
50
    }
255
256
    // - PS 4.0 preview data is fetched from ThumbnailResource
257
    // - PS >= 5.0 preview data is fetched from ThumbnailResource2
258
354
    case kPhotoshopResourceID::ThumbnailResource:
259
378
    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
378
      byte buf[28];
277
378
      if (io_->read(buf, 28) != 28) {
278
27
        throw Error(ErrorCode::kerNotAnImage, "Photoshop");
279
27
      }
280
351
      NativePreview nativePreview;
281
351
      nativePreview.position_ = io_->tell();
282
351
      nativePreview.size_ = getLong(buf + 20, bigEndian);  // compressedsize
283
351
      nativePreview.width_ = getLong(buf + 4, bigEndian);
284
351
      nativePreview.height_ = getLong(buf + 8, bigEndian);
285
351
      const uint32_t format = getLong(buf + 0, bigEndian);
286
287
351
      if (nativePreview.size_ > 0 && nativePreview.position_ > 0) {
288
294
        io_->seek(static_cast<long>(nativePreview.size_), BasicIo::cur);
289
294
        if (io_->error() || io_->eof())
290
30
          throw Error(ErrorCode::kerFailedToReadImageData);
291
292
        // unsupported format of native preview
293
264
        if (format != 1)
294
239
          break;
295
25
        nativePreview.filter_ = "";
296
25
        nativePreview.mimeType_ = "image/jpeg";
297
25
        nativePreviews_.push_back(std::move(nativePreview));
298
25
      }
299
82
      break;
300
351
    }
301
302
181
    default:
303
181
      break;
304
782
  }
305
782
}  // PsdImage::readResourceBlock
306
307
99
void PsdImage::writeMetadata() {
308
99
  if (io_->open() != 0) {
309
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
310
0
  }
311
99
  IoCloser closer(*io_);
312
99
  MemIo tempIo;
313
314
99
  doWriteMetadata(tempIo);  // may throw
315
99
  io_->close();
316
99
  io_->transfer(tempIo);  // may throw
317
318
99
}  // PsdImage::writeMetadata
319
320
99
void PsdImage::doWriteMetadata(BasicIo& outIo) {
321
99
  if (!io_->isopen())
322
0
    throw Error(ErrorCode::kerInputDataReadFailed);
323
99
  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
99
  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
99
  io_->seek(0, BasicIo::beg);  // rewind
339
340
99
  DataBuf lbuf(4096);
341
99
  byte buf[8];
342
343
  // Get Photoshop header from original file
344
99
  byte psd_head[26];
345
99
  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
99
  if (outIo.write(psd_head, 26) != 26)
350
0
    throw Error(ErrorCode::kerImageWriteFailed);
351
352
  // Read colorDataLength from original PSD
353
99
  if (io_->read(buf, 4) != 4)
354
0
    throw Error(ErrorCode::kerNotAnImage, "Photoshop");
355
356
99
  uint32_t colorDataLength = getULong(buf, bigEndian);
357
358
  // Write colorDataLength
359
99
  ul2Data(buf, colorDataLength, bigEndian);
360
99
  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
99
  size_t readTotal = 0;
367
191
  while (readTotal < colorDataLength) {
368
92
    auto toRead = std::min<size_t>(colorDataLength - readTotal, lbuf.size());
369
92
    if (io_->read(lbuf.data(), toRead) != toRead)
370
0
      throw Error(ErrorCode::kerNotAnImage, "Photoshop");
371
92
    readTotal += toRead;
372
92
    if (outIo.write(lbuf.c_data(), toRead) != toRead)
373
0
      throw Error(ErrorCode::kerImageWriteFailed);
374
92
  }
375
99
  if (outIo.error())
376
0
    throw Error(ErrorCode::kerImageWriteFailed);
377
378
99
  const size_t resLenOffset = io_->tell();  // remember for later update
379
380
  // Read length of all resource blocks from original PSD
381
99
  if (io_->read(buf, 4) != 4)
382
0
    throw Error(ErrorCode::kerNotAnImage, "Photoshop");
383
384
99
  uint32_t oldResLength = getULong(buf, bigEndian);
385
99
  uint32_t newResLength = 0;
386
387
  // Write oldResLength (will be updated later)
388
99
  ul2Data(buf, oldResLength, bigEndian);
389
99
  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
99
  bool iptcDone = false;
401
99
  bool exifDone = false;
402
99
  bool xmpDone = false;
403
251
  while (oldResLength > 0) {
404
227
    if (io_->read(buf, 8) != 8)
405
0
      throw Error(ErrorCode::kerNotAnImage, "Photoshop");
406
407
    // read resource type and ID
408
227
    uint32_t resourceType = getULong(buf, bigEndian);
409
410
227
    if (!Photoshop::isIrb(buf)) {
411
68
      throw Error(ErrorCode::kerNotAnImage, "Photoshop");  // bad resource type
412
68
    }
413
159
    uint16_t resourceId = getUShort(buf + 4, bigEndian);
414
159
    uint32_t resourceNameLength = buf[6];
415
159
    uint32_t adjResourceNameLen = resourceNameLength & ~1;
416
159
    unsigned char resourceNameFirstChar = buf[7];
417
418
    // read rest of resource name, plus any padding
419
159
    DataBuf resName(256);
420
159
    if (io_->read(resName.data(), adjResourceNameLen) != adjResourceNameLen)
421
1
      throw Error(ErrorCode::kerNotAnImage, "Photoshop");
422
423
    // read resource size (actual length w/o padding!)
424
158
    if (io_->read(buf, 4) != 4)
425
0
      throw Error(ErrorCode::kerNotAnImage, "Photoshop");
426
427
158
    uint32_t resourceSize = getULong(buf, bigEndian);
428
158
    uint32_t pResourceSize = (resourceSize + 1) & ~1;  // padded resource size
429
158
    const size_t curOffset = io_->tell();
430
431
    // Write IPTC_NAA resource block
432
158
    if (resourceId >= kPhotoshopResourceID::IPTC_NAA && !iptcDone) {
433
78
      newResLength += writeIptcData(iptcData_, outIo);
434
78
      iptcDone = true;
435
78
    }
436
437
    // Write ExifInfo resource block
438
80
    else if (resourceId >= kPhotoshopResourceID::ExifInfo && !exifDone) {
439
20
      newResLength += writeExifData(exifData_, outIo);
440
20
      exifDone = true;
441
20
    }
442
443
    // Write XMPpacket resource block
444
60
    else if (resourceId >= kPhotoshopResourceID::XMPPacket && !xmpDone) {
445
12
      newResLength += writeXmpData(xmpData_, outIo);
446
12
      xmpDone = true;
447
12
    }
448
449
    // Copy all other resource blocks
450
158
    if (resourceId != kPhotoshopResourceID::IPTC_NAA && resourceId != kPhotoshopResourceID::ExifInfo &&
451
97
        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
79
      ul2Data(buf, resourceType, bigEndian);
459
79
      if (outIo.write(buf, 4) != 4)
460
0
        throw Error(ErrorCode::kerImageWriteFailed);
461
79
      us2Data(buf, resourceId, bigEndian);
462
79
      if (outIo.write(buf, 2) != 2)
463
0
        throw Error(ErrorCode::kerImageWriteFailed);
464
      // Write resource name as Pascal string
465
79
      buf[0] = resourceNameLength & 0x00ff;
466
79
      if (outIo.write(buf, 1) != 1)
467
0
        throw Error(ErrorCode::kerImageWriteFailed);
468
79
      buf[0] = resourceNameFirstChar;
469
79
      if (outIo.write(buf, 1) != 1)
470
0
        throw Error(ErrorCode::kerImageWriteFailed);
471
79
      if (outIo.write(resName.c_data(), adjResourceNameLen) != static_cast<size_t>(adjResourceNameLen))
472
0
        throw Error(ErrorCode::kerImageWriteFailed);
473
79
      ul2Data(buf, resourceSize, bigEndian);
474
79
      if (outIo.write(buf, 4) != 4)
475
0
        throw Error(ErrorCode::kerImageWriteFailed);
476
477
79
      readTotal = 0;
478
138
      while (readTotal < pResourceSize) {
479
        /// \todo almost same code as in lines 403-410. Factor out & reuse!
480
65
        auto toRead = std::min<size_t>(pResourceSize - readTotal, lbuf.size());
481
65
        if (io_->read(lbuf.data(), toRead) != toRead) {
482
6
          throw Error(ErrorCode::kerNotAnImage, "Photoshop");
483
6
        }
484
59
        readTotal += toRead;
485
59
        if (outIo.write(lbuf.c_data(), toRead) != toRead)
486
0
          throw Error(ErrorCode::kerImageWriteFailed);
487
59
      }
488
73
      if (outIo.error())
489
0
        throw Error(ErrorCode::kerImageWriteFailed);
490
73
      newResLength += pResourceSize + adjResourceNameLen + 12;
491
73
    }
492
493
152
    io_->seek(curOffset + pResourceSize, BasicIo::beg);
494
152
    oldResLength -= (12 + adjResourceNameLen + pResourceSize);
495
152
  }
496
497
  // Append IPTC_NAA resource block, if not yet written
498
24
  if (!iptcDone) {
499
16
    newResLength += writeIptcData(iptcData_, outIo);
500
16
    iptcDone = true;
501
16
  }
502
503
  // Append ExifInfo resource block, if not yet written
504
24
  if (!exifDone) {
505
22
    newResLength += writeExifData(exifData_, outIo);
506
22
  }
507
508
  // Append XmpPacket resource block, if not yet written
509
24
  if (!xmpDone) {
510
23
    newResLength += writeXmpData(xmpData_, outIo);
511
23
  }
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
24
  io_->populateFakeData();
516
517
  // Copy remaining data
518
24
  size_t readSize = io_->read(lbuf.data(), lbuf.size());
519
171
  while (readSize != 0) {
520
147
    if (outIo.write(lbuf.c_data(), readSize) != readSize)
521
0
      throw Error(ErrorCode::kerImageWriteFailed);
522
147
    readSize = io_->read(lbuf.data(), lbuf.size());
523
147
  }
524
24
  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
24
  outIo.seek(resLenOffset, BasicIo::beg);
532
24
  ul2Data(buf, newResLength, bigEndian);
533
24
  if (outIo.write(buf, 4) != 4)
534
0
    throw Error(ErrorCode::kerImageWriteFailed);
535
536
24
}  // PsdImage::doWriteMetadata
537
538
94
uint32_t PsdImage::writeIptcData(const IptcData& iptcData, BasicIo& out) {
539
94
  uint32_t resLength = 0;
540
94
  byte buf[8];
541
542
94
  if (!iptcData.empty()) {
543
10
    DataBuf rawIptc = IptcParser::encode(iptcData);
544
10
    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
10
      if (out.write(reinterpret_cast<const byte*>(Photoshop::irbId_.front()), 4) != 4)
550
0
        throw Error(ErrorCode::kerImageWriteFailed);
551
10
      us2Data(buf, kPhotoshopResourceID::IPTC_NAA, bigEndian);
552
10
      if (out.write(buf, 2) != 2)
553
0
        throw Error(ErrorCode::kerImageWriteFailed);
554
10
      us2Data(buf, 0, bigEndian);  // NULL resource name
555
10
      if (out.write(buf, 2) != 2)
556
0
        throw Error(ErrorCode::kerImageWriteFailed);
557
10
      ul2Data(buf, static_cast<uint32_t>(rawIptc.size()), bigEndian);
558
10
      if (out.write(buf, 4) != 4)
559
0
        throw Error(ErrorCode::kerImageWriteFailed);
560
      // Write encoded Iptc data
561
10
      if (out.write(rawIptc.c_data(), rawIptc.size()) != rawIptc.size())
562
0
        throw Error(ErrorCode::kerImageWriteFailed);
563
10
      resLength += static_cast<uint32_t>(rawIptc.size()) + 12;
564
10
      if (rawIptc.size() & 1)  // even padding
565
9
      {
566
9
        buf[0] = 0;
567
9
        if (out.write(buf, 1) != 1)
568
0
          throw Error(ErrorCode::kerImageWriteFailed);
569
9
        resLength++;
570
9
      }
571
10
    }
572
10
  }
573
94
  return resLength;
574
94
}  // PsdImage::writeIptcData
575
576
42
uint32_t PsdImage::writeExifData(ExifData& exifData, BasicIo& out) {
577
42
  uint32_t resLength = 0;
578
42
  byte buf[8];
579
580
42
  if (!exifData.empty()) {
581
1
    Blob blob;
582
1
    ByteOrder bo = byteOrder();
583
1
    if (bo == invalidByteOrder) {
584
0
      bo = littleEndian;
585
0
      setByteOrder(bo);
586
0
    }
587
1
    ExifParser::encode(blob, bo, exifData);
588
589
1
    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
1
      if (out.write(reinterpret_cast<const byte*>(Photoshop::irbId_.front()), 4) != 4)
595
0
        throw Error(ErrorCode::kerImageWriteFailed);
596
1
      us2Data(buf, kPhotoshopResourceID::ExifInfo, bigEndian);
597
1
      if (out.write(buf, 2) != 2)
598
0
        throw Error(ErrorCode::kerImageWriteFailed);
599
1
      us2Data(buf, 0, bigEndian);  // NULL resource name
600
1
      if (out.write(buf, 2) != 2)
601
0
        throw Error(ErrorCode::kerImageWriteFailed);
602
1
      ul2Data(buf, static_cast<uint32_t>(blob.size()), bigEndian);
603
1
      if (out.write(buf, 4) != 4)
604
0
        throw Error(ErrorCode::kerImageWriteFailed);
605
      // Write encoded Exif data
606
1
      if (out.write(blob.data(), blob.size()) != blob.size())
607
0
        throw Error(ErrorCode::kerImageWriteFailed);
608
1
      resLength += static_cast<long>(blob.size()) + 12;
609
1
      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
1
    }
617
1
  }
618
42
  return resLength;
619
42
}  // PsdImage::writeExifData
620
621
35
uint32_t PsdImage::writeXmpData(const XmpData& xmpData, BasicIo& out) const {
622
35
  std::string xmpPacket;
623
35
  uint32_t resLength = 0;
624
35
  byte buf[8];
625
626
#ifdef EXIV2_DEBUG_MESSAGES
627
  std::cerr << "writeXmpFromPacket(): " << writeXmpFromPacket() << "\n";
628
#endif
629
  //        writeXmpFromPacket(true);
630
35
  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
35
  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
2
    if (out.write(reinterpret_cast<const byte*>(Photoshop::irbId_.front()), 4) != 4)
642
0
      throw Error(ErrorCode::kerImageWriteFailed);
643
2
    us2Data(buf, kPhotoshopResourceID::XMPPacket, bigEndian);
644
2
    if (out.write(buf, 2) != 2)
645
0
      throw Error(ErrorCode::kerImageWriteFailed);
646
2
    us2Data(buf, 0, bigEndian);  // NULL resource name
647
2
    if (out.write(buf, 2) != 2)
648
0
      throw Error(ErrorCode::kerImageWriteFailed);
649
2
    ul2Data(buf, static_cast<uint32_t>(xmpPacket.size()), bigEndian);
650
2
    if (out.write(buf, 4) != 4)
651
0
      throw Error(ErrorCode::kerImageWriteFailed);
652
    // Write XMPPacket
653
2
    if (out.write(reinterpret_cast<const byte*>(xmpPacket.data()), xmpPacket.size()) != xmpPacket.size())
654
0
      throw Error(ErrorCode::kerImageWriteFailed);
655
2
    if (out.error())
656
0
      throw Error(ErrorCode::kerImageWriteFailed);
657
2
    resLength += static_cast<uint32_t>(xmpPacket.size()) + 12;
658
2
    if (xmpPacket.size() & 1)  // even padding
659
1
    {
660
1
      buf[0] = 0;
661
1
      if (out.write(buf, 1) != 1)
662
0
        throw Error(ErrorCode::kerImageWriteFailed);
663
1
      resLength++;
664
1
    }
665
2
  }
666
35
  return resLength;
667
35
}  // PsdImage::writeXmpData
668
669
// *************************************************************************
670
// free functions
671
655
Image::UniquePtr newPsdInstance(BasicIo::UniquePtr io, bool /*create*/) {
672
655
  auto image = std::make_unique<PsdImage>(std::move(io));
673
655
  if (!image->good()) {
674
7
    return nullptr;
675
7
  }
676
648
  return image;
677
655
}
678
679
22.1k
bool isPsdType(BasicIo& iIo, bool advance) {
680
22.1k
  const int32_t len = 6;
681
22.1k
  const std::array<byte, len> PsdHeader{'8', 'B', 'P', 'S', 0, 1};
682
22.1k
  std::array<byte, len> buf;
683
22.1k
  iIo.read(buf.data(), len);
684
22.1k
  if (iIo.error() || iIo.eof()) {
685
445
    return false;
686
445
  }
687
21.7k
  bool matched = buf == PsdHeader;
688
21.7k
  if (!advance || !matched) {
689
21.6k
    iIo.seek(-len, BasicIo::cur);
690
21.6k
  }
691
692
21.7k
  return matched;
693
22.1k
}
694
}  // namespace Exiv2