Coverage Report

Created: 2026-03-12 07:14

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
76
PsdImage::PsdImage(BasicIo::UniquePtr io) : Image(ImageType::psd, mdExif | mdIptc | mdXmp, std::move(io)) {
101
76
}  // PsdImage::PsdImage
102
103
25
std::string PsdImage::mimeType() const {
104
25
  return "image/x-photoshop";
105
25
}
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
66
void PsdImage::readMetadata() {
113
#ifdef EXIV2_DEBUG_MESSAGES
114
  std::cerr << "Exiv2::PsdImage::readMetadata: Reading Photoshop file " << io_->path() << "\n";
115
#endif
116
66
  if (io_->open() != 0) {
117
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
118
0
  }
119
66
  IoCloser closer(*io_);
120
  // Ensure that this is the correct image type
121
66
  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
66
  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
66
  byte buf[26];
144
66
  if (io_->read(buf, 26) != 26) {
145
0
    throw Error(ErrorCode::kerNotAnImage, "Photoshop");
146
0
  }
147
66
  pixelWidth_ = getLong(buf + 18, bigEndian);
148
66
  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
66
  if (io_->read(buf, 4) != 4) {
153
0
    throw Error(ErrorCode::kerNotAnImage, "Photoshop");
154
0
  }
155
156
  // skip it
157
66
  if (io_->seek(getULong(buf, bigEndian), BasicIo::cur)) {
158
10
    throw Error(ErrorCode::kerNotAnImage, "Photoshop");
159
10
  }
160
161
  // after the color data section, comes a list of resource blocks, preceded by the total byte size
162
56
  if (io_->read(buf, 4) != 4) {
163
10
    throw Error(ErrorCode::kerNotAnImage, "Photoshop");
164
10
  }
165
46
  uint32_t resourcesLength = getULong(buf, bigEndian);
166
46
  Internal::enforce(resourcesLength < io_->size(), Exiv2::ErrorCode::kerCorruptedMetadata);
167
168
60
  while (resourcesLength > 0) {
169
29
    Internal::enforce(resourcesLength >= 8, Exiv2::ErrorCode::kerCorruptedMetadata);
170
29
    resourcesLength -= 8;
171
29
    if (io_->read(buf, 8) != 8) {
172
0
      throw Error(ErrorCode::kerNotAnImage, "Photoshop");
173
0
    }
174
175
29
    if (!Photoshop::isIrb(buf)) {
176
15
      break;  // bad resource type
177
15
    }
178
14
    uint16_t resourceId = getUShort(buf + 4, bigEndian);
179
14
    uint32_t resourceNameLength = buf[6] & ~1;
180
181
    // skip the resource name, plus any padding
182
14
    Internal::enforce(resourceNameLength <= resourcesLength, Exiv2::ErrorCode::kerCorruptedMetadata);
183
14
    resourcesLength -= resourceNameLength;
184
14
    io_->seek(resourceNameLength, BasicIo::cur);
185
186
    // read resource size
187
14
    Internal::enforce(resourcesLength >= 4, Exiv2::ErrorCode::kerCorruptedMetadata);
188
14
    resourcesLength -= 4;
189
14
    if (io_->read(buf, 4) != 4) {
190
0
      throw Error(ErrorCode::kerNotAnImage, "Photoshop");
191
0
    }
192
14
    uint32_t resourceSize = getULong(buf, bigEndian);
193
14
    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
14
    Internal::enforce(resourceSize <= resourcesLength, Exiv2::ErrorCode::kerCorruptedMetadata);
201
14
    readResourceBlock(resourceId, resourceSize);
202
14
    resourceSize = (resourceSize + 1) & ~1;  // pad to even
203
14
    Internal::enforce(resourceSize <= resourcesLength, Exiv2::ErrorCode::kerCorruptedMetadata);
204
14
    resourcesLength -= resourceSize;
205
14
    io_->seek(curOffset + resourceSize, BasicIo::beg);
206
14
  }
207
208
46
}  // PsdImage::readMetadata
209
210
4
void PsdImage::readResourceBlock(uint16_t resourceId, uint32_t resourceSize) {
211
4
  switch (resourceId) {
212
4
    case kPhotoshopResourceID::IPTC_NAA: {
213
4
      DataBuf rawIPTC(resourceSize);
214
4
      io_->read(rawIPTC.data(), rawIPTC.size());
215
4
      if (io_->error() || io_->eof())
216
0
        throw Error(ErrorCode::kerFailedToReadImageData);
217
4
      if (IptcParser::decode(iptcData_, rawIPTC.c_data(), rawIPTC.size())) {
218
1
#ifndef SUPPRESS_WARNINGS
219
1
        EXV_WARNING << "Failed to decode IPTC metadata.\n";
220
1
#endif
221
1
        iptcData_.clear();
222
1
      }
223
4
      break;
224
4
    }
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
      Internal::enforce(nativePreview.size_ <= static_cast<size_t>(std::numeric_limits<long>::max()),
288
0
                        Exiv2::ErrorCode::kerCorruptedMetadata);
289
290
0
      if (nativePreview.size_ > 0 && nativePreview.position_ > 0) {
291
0
        io_->seek(static_cast<long>(nativePreview.size_), BasicIo::cur);
292
0
        if (io_->error() || io_->eof())
293
0
          throw Error(ErrorCode::kerFailedToReadImageData);
294
295
        // unsupported format of native preview
296
0
        if (format != 1)
297
0
          break;
298
0
        nativePreview.filter_ = "";
299
0
        nativePreview.mimeType_ = "image/jpeg";
300
0
        nativePreviews_.push_back(std::move(nativePreview));
301
0
      }
302
0
      break;
303
0
    }
304
305
0
    default:
306
0
      break;
307
4
  }
308
4
}  // PsdImage::readResourceBlock
309
310
0
void PsdImage::writeMetadata() {
311
0
  if (io_->open() != 0) {
312
0
    throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError());
313
0
  }
314
0
  IoCloser closer(*io_);
315
0
  MemIo tempIo;
316
317
0
  doWriteMetadata(tempIo);  // may throw
318
0
  io_->close();
319
0
  io_->transfer(tempIo);  // may throw
320
321
0
}  // PsdImage::writeMetadata
322
323
0
void PsdImage::doWriteMetadata(BasicIo& outIo) {
324
0
  if (!io_->isopen())
325
0
    throw Error(ErrorCode::kerInputDataReadFailed);
326
0
  if (!outIo.isopen())
327
0
    throw Error(ErrorCode::kerImageWriteFailed);
328
329
#ifdef EXIV2_DEBUG_MESSAGES
330
  std::cout << "Exiv2::PsdImage::doWriteMetadata: Writing PSD file " << io_->path() << "\n";
331
  std::cout << "Exiv2::PsdImage::doWriteMetadata: tmp file created " << outIo.path() << "\n";
332
#endif
333
334
  // Ensure that this is the correct image type
335
0
  if (!isPsdType(*io_, true)) {
336
0
    if (io_->error() || io_->eof())
337
0
      throw Error(ErrorCode::kerInputDataReadFailed);
338
0
    throw Error(ErrorCode::kerNoImageInInputData);
339
0
  }
340
341
0
  io_->seek(0, BasicIo::beg);  // rewind
342
343
0
  DataBuf lbuf(4096);
344
0
  byte buf[8];
345
346
  // Get Photoshop header from original file
347
0
  byte psd_head[26];
348
0
  if (io_->read(psd_head, 26) != 26)
349
0
    throw Error(ErrorCode::kerNotAnImage, "Photoshop");
350
351
  // Write Photoshop header data out to new PSD file
352
0
  if (outIo.write(psd_head, 26) != 26)
353
0
    throw Error(ErrorCode::kerImageWriteFailed);
354
355
  // Read colorDataLength from original PSD
356
0
  if (io_->read(buf, 4) != 4)
357
0
    throw Error(ErrorCode::kerNotAnImage, "Photoshop");
358
359
0
  uint32_t colorDataLength = getULong(buf, bigEndian);
360
361
  // Write colorDataLength
362
0
  ul2Data(buf, colorDataLength, bigEndian);
363
0
  if (outIo.write(buf, 4) != 4)
364
0
    throw Error(ErrorCode::kerImageWriteFailed);
365
#ifdef EXIV2_DEBUG_MESSAGES
366
  std::cerr << std::dec << "colorDataLength: " << colorDataLength << "\n";
367
#endif
368
  // Copy colorData
369
0
  size_t readTotal = 0;
370
0
  while (readTotal < colorDataLength) {
371
0
    auto toRead = std::min<size_t>(colorDataLength - readTotal, lbuf.size());
372
0
    if (io_->read(lbuf.data(), toRead) != toRead)
373
0
      throw Error(ErrorCode::kerNotAnImage, "Photoshop");
374
0
    readTotal += toRead;
375
0
    if (outIo.write(lbuf.c_data(), toRead) != toRead)
376
0
      throw Error(ErrorCode::kerImageWriteFailed);
377
0
  }
378
0
  if (outIo.error())
379
0
    throw Error(ErrorCode::kerImageWriteFailed);
380
381
0
  const size_t resLenOffset = io_->tell();  // remember for later update
382
383
  // Read length of all resource blocks from original PSD
384
0
  if (io_->read(buf, 4) != 4)
385
0
    throw Error(ErrorCode::kerNotAnImage, "Photoshop");
386
387
0
  uint32_t oldResLength = getULong(buf, bigEndian);
388
0
  uint32_t newResLength = 0;
389
390
  // Write oldResLength (will be updated later)
391
0
  ul2Data(buf, oldResLength, bigEndian);
392
0
  if (outIo.write(buf, 4) != 4)
393
0
    throw Error(ErrorCode::kerImageWriteFailed);
394
395
#ifdef EXIV2_DEBUG_MESSAGES
396
  std::cerr << std::dec << "oldResLength: " << oldResLength << "\n";
397
#endif
398
399
  // Iterate over original resource blocks.
400
  // Replace or insert IPTC, EXIF and XMP
401
  // Original resource blocks assumed to be sorted ASC
402
403
0
  bool iptcDone = false;
404
0
  bool exifDone = false;
405
0
  bool xmpDone = false;
406
0
  while (oldResLength > 0) {
407
0
    if (io_->read(buf, 8) != 8)
408
0
      throw Error(ErrorCode::kerNotAnImage, "Photoshop");
409
410
    // read resource type and ID
411
0
    uint32_t resourceType = getULong(buf, bigEndian);
412
413
0
    if (!Photoshop::isIrb(buf)) {
414
0
      throw Error(ErrorCode::kerNotAnImage, "Photoshop");  // bad resource type
415
0
    }
416
0
    uint16_t resourceId = getUShort(buf + 4, bigEndian);
417
0
    uint32_t resourceNameLength = buf[6];
418
0
    uint32_t adjResourceNameLen = resourceNameLength & ~1;
419
0
    unsigned char resourceNameFirstChar = buf[7];
420
421
    // read rest of resource name, plus any padding
422
0
    DataBuf resName(256);
423
0
    if (io_->read(resName.data(), adjResourceNameLen) != adjResourceNameLen)
424
0
      throw Error(ErrorCode::kerNotAnImage, "Photoshop");
425
426
    // read resource size (actual length w/o padding!)
427
0
    if (io_->read(buf, 4) != 4)
428
0
      throw Error(ErrorCode::kerNotAnImage, "Photoshop");
429
430
0
    uint32_t resourceSize = getULong(buf, bigEndian);
431
0
    uint32_t pResourceSize = (resourceSize + 1) & ~1;  // padded resource size
432
0
    const size_t curOffset = io_->tell();
433
434
    // Write IPTC_NAA resource block
435
0
    if (resourceId >= kPhotoshopResourceID::IPTC_NAA && !iptcDone) {
436
0
      newResLength += writeIptcData(iptcData_, outIo);
437
0
      iptcDone = true;
438
0
    }
439
440
    // Write ExifInfo resource block
441
0
    else if (resourceId >= kPhotoshopResourceID::ExifInfo && !exifDone) {
442
0
      newResLength += writeExifData(exifData_, outIo);
443
0
      exifDone = true;
444
0
    }
445
446
    // Write XMPpacket resource block
447
0
    else if (resourceId >= kPhotoshopResourceID::XMPPacket && !xmpDone) {
448
0
      newResLength += writeXmpData(xmpData_, outIo);
449
0
      xmpDone = true;
450
0
    }
451
452
    // Copy all other resource blocks
453
0
    if (resourceId != kPhotoshopResourceID::IPTC_NAA && resourceId != kPhotoshopResourceID::ExifInfo &&
454
0
        resourceId != kPhotoshopResourceID::XMPPacket) {
455
#ifdef EXIV2_DEBUG_MESSAGES
456
      std::cerr << std::hex << "copy : resourceType: " << resourceType << "\n";
457
      std::cerr << std::hex << "copy : resourceId: " << resourceId << "\n";
458
      std::cerr << std::dec;
459
#endif
460
      // Copy resource block to new PSD file
461
0
      ul2Data(buf, resourceType, bigEndian);
462
0
      if (outIo.write(buf, 4) != 4)
463
0
        throw Error(ErrorCode::kerImageWriteFailed);
464
0
      us2Data(buf, resourceId, bigEndian);
465
0
      if (outIo.write(buf, 2) != 2)
466
0
        throw Error(ErrorCode::kerImageWriteFailed);
467
      // Write resource name as Pascal string
468
0
      buf[0] = resourceNameLength & 0x00ff;
469
0
      if (outIo.write(buf, 1) != 1)
470
0
        throw Error(ErrorCode::kerImageWriteFailed);
471
0
      buf[0] = resourceNameFirstChar;
472
0
      if (outIo.write(buf, 1) != 1)
473
0
        throw Error(ErrorCode::kerImageWriteFailed);
474
0
      if (outIo.write(resName.c_data(), adjResourceNameLen) != static_cast<size_t>(adjResourceNameLen))
475
0
        throw Error(ErrorCode::kerImageWriteFailed);
476
0
      ul2Data(buf, resourceSize, bigEndian);
477
0
      if (outIo.write(buf, 4) != 4)
478
0
        throw Error(ErrorCode::kerImageWriteFailed);
479
480
0
      readTotal = 0;
481
0
      while (readTotal < pResourceSize) {
482
        /// \todo almost same code as in lines 403-410. Factor out & reuse!
483
0
        auto toRead = std::min<size_t>(pResourceSize - readTotal, lbuf.size());
484
0
        if (io_->read(lbuf.data(), toRead) != toRead) {
485
0
          throw Error(ErrorCode::kerNotAnImage, "Photoshop");
486
0
        }
487
0
        readTotal += toRead;
488
0
        if (outIo.write(lbuf.c_data(), toRead) != toRead)
489
0
          throw Error(ErrorCode::kerImageWriteFailed);
490
0
      }
491
0
      if (outIo.error())
492
0
        throw Error(ErrorCode::kerImageWriteFailed);
493
0
      newResLength += pResourceSize + adjResourceNameLen + 12;
494
0
    }
495
496
0
    io_->seek(curOffset + pResourceSize, BasicIo::beg);
497
0
    oldResLength -= (12 + adjResourceNameLen + pResourceSize);
498
0
  }
499
500
  // Append IPTC_NAA resource block, if not yet written
501
0
  if (!iptcDone) {
502
0
    newResLength += writeIptcData(iptcData_, outIo);
503
0
    iptcDone = true;
504
0
  }
505
506
  // Append ExifInfo resource block, if not yet written
507
0
  if (!exifDone) {
508
0
    newResLength += writeExifData(exifData_, outIo);
509
0
  }
510
511
  // Append XmpPacket resource block, if not yet written
512
0
  if (!xmpDone) {
513
0
    newResLength += writeXmpData(xmpData_, outIo);
514
0
  }
515
516
  // Populate the fake data, only make sense for remoteio, httpio and sshio.
517
  // it avoids allocating memory for parts of the file that contain image-date.
518
0
  io_->populateFakeData();
519
520
  // Copy remaining data
521
0
  size_t readSize = io_->read(lbuf.data(), lbuf.size());
522
0
  while (readSize != 0) {
523
0
    if (outIo.write(lbuf.c_data(), readSize) != readSize)
524
0
      throw Error(ErrorCode::kerImageWriteFailed);
525
0
    readSize = io_->read(lbuf.data(), lbuf.size());
526
0
  }
527
0
  if (outIo.error())
528
0
    throw Error(ErrorCode::kerImageWriteFailed);
529
530
    // Update length of resources
531
#ifdef EXIV2_DEBUG_MESSAGES
532
  std::cerr << "newResLength: " << newResLength << "\n";
533
#endif
534
0
  outIo.seek(resLenOffset, BasicIo::beg);
535
0
  ul2Data(buf, newResLength, bigEndian);
536
0
  if (outIo.write(buf, 4) != 4)
537
0
    throw Error(ErrorCode::kerImageWriteFailed);
538
539
0
}  // PsdImage::doWriteMetadata
540
541
0
uint32_t PsdImage::writeIptcData(const IptcData& iptcData, BasicIo& out) {
542
0
  uint32_t resLength = 0;
543
0
  byte buf[8];
544
545
0
  if (!iptcData.empty()) {
546
0
    DataBuf rawIptc = IptcParser::encode(iptcData);
547
0
    if (!rawIptc.empty()) {
548
#ifdef EXIV2_DEBUG_MESSAGES
549
      std::cerr << std::hex << "write: resourceId: " << kPhotoshopResourceID::IPTC_NAA << "\n";
550
      std::cerr << std::dec << "Writing IPTC_NAA: size: " << rawIptc.size() << "\n";
551
#endif
552
0
      if (out.write(reinterpret_cast<const byte*>(Photoshop::irbId_.front()), 4) != 4)
553
0
        throw Error(ErrorCode::kerImageWriteFailed);
554
0
      us2Data(buf, kPhotoshopResourceID::IPTC_NAA, bigEndian);
555
0
      if (out.write(buf, 2) != 2)
556
0
        throw Error(ErrorCode::kerImageWriteFailed);
557
0
      us2Data(buf, 0, bigEndian);  // NULL resource name
558
0
      if (out.write(buf, 2) != 2)
559
0
        throw Error(ErrorCode::kerImageWriteFailed);
560
0
      ul2Data(buf, static_cast<uint32_t>(rawIptc.size()), bigEndian);
561
0
      if (out.write(buf, 4) != 4)
562
0
        throw Error(ErrorCode::kerImageWriteFailed);
563
      // Write encoded Iptc data
564
0
      if (out.write(rawIptc.c_data(), rawIptc.size()) != rawIptc.size())
565
0
        throw Error(ErrorCode::kerImageWriteFailed);
566
0
      resLength += static_cast<uint32_t>(rawIptc.size()) + 12;
567
0
      if (rawIptc.size() & 1)  // even padding
568
0
      {
569
0
        buf[0] = 0;
570
0
        if (out.write(buf, 1) != 1)
571
0
          throw Error(ErrorCode::kerImageWriteFailed);
572
0
        resLength++;
573
0
      }
574
0
    }
575
0
  }
576
0
  return resLength;
577
0
}  // PsdImage::writeIptcData
578
579
0
uint32_t PsdImage::writeExifData(ExifData& exifData, BasicIo& out) {
580
0
  uint32_t resLength = 0;
581
0
  byte buf[8];
582
583
0
  if (!exifData.empty()) {
584
0
    Blob blob;
585
0
    ByteOrder bo = byteOrder();
586
0
    if (bo == invalidByteOrder) {
587
0
      bo = littleEndian;
588
0
      setByteOrder(bo);
589
0
    }
590
0
    ExifParser::encode(blob, bo, exifData);
591
592
0
    if (!blob.empty()) {
593
#ifdef EXIV2_DEBUG_MESSAGES
594
      std::cerr << std::hex << "write: resourceId: " << kPhotoshopResourceID::ExifInfo << "\n";
595
      std::cerr << std::dec << "Writing ExifInfo: size: " << blob.size() << "\n";
596
#endif
597
0
      if (out.write(reinterpret_cast<const byte*>(Photoshop::irbId_.front()), 4) != 4)
598
0
        throw Error(ErrorCode::kerImageWriteFailed);
599
0
      us2Data(buf, kPhotoshopResourceID::ExifInfo, bigEndian);
600
0
      if (out.write(buf, 2) != 2)
601
0
        throw Error(ErrorCode::kerImageWriteFailed);
602
0
      us2Data(buf, 0, bigEndian);  // NULL resource name
603
0
      if (out.write(buf, 2) != 2)
604
0
        throw Error(ErrorCode::kerImageWriteFailed);
605
0
      ul2Data(buf, static_cast<uint32_t>(blob.size()), bigEndian);
606
0
      if (out.write(buf, 4) != 4)
607
0
        throw Error(ErrorCode::kerImageWriteFailed);
608
      // Write encoded Exif data
609
0
      if (out.write(blob.data(), blob.size()) != blob.size())
610
0
        throw Error(ErrorCode::kerImageWriteFailed);
611
0
      resLength += static_cast<long>(blob.size()) + 12;
612
0
      if (blob.size() & 1)  // even padding
613
0
      {
614
0
        buf[0] = 0;
615
0
        if (out.write(buf, 1) != 1)
616
0
          throw Error(ErrorCode::kerImageWriteFailed);
617
0
        resLength++;
618
0
      }
619
0
    }
620
0
  }
621
0
  return resLength;
622
0
}  // PsdImage::writeExifData
623
624
0
uint32_t PsdImage::writeXmpData(const XmpData& xmpData, BasicIo& out) const {
625
0
  std::string xmpPacket;
626
0
  uint32_t resLength = 0;
627
0
  byte buf[8];
628
629
#ifdef EXIV2_DEBUG_MESSAGES
630
  std::cerr << "writeXmpFromPacket(): " << writeXmpFromPacket() << "\n";
631
#endif
632
  //        writeXmpFromPacket(true);
633
0
  if (!writeXmpFromPacket() && XmpParser::encode(xmpPacket, xmpData) > 1) {
634
0
#ifndef SUPPRESS_WARNINGS
635
0
    EXV_ERROR << "Failed to encode XMP metadata.\n";
636
0
#endif
637
0
  }
638
639
0
  if (!xmpPacket.empty()) {
640
#ifdef EXIV2_DEBUG_MESSAGES
641
    std::cerr << std::hex << "write: resourceId: " << kPhotoshopResourceID::XMPPacket << "\n";
642
    std::cerr << std::dec << "Writing XMPPacket: size: " << xmpPacket.size() << "\n";
643
#endif
644
0
    if (out.write(reinterpret_cast<const byte*>(Photoshop::irbId_.front()), 4) != 4)
645
0
      throw Error(ErrorCode::kerImageWriteFailed);
646
0
    us2Data(buf, kPhotoshopResourceID::XMPPacket, bigEndian);
647
0
    if (out.write(buf, 2) != 2)
648
0
      throw Error(ErrorCode::kerImageWriteFailed);
649
0
    us2Data(buf, 0, bigEndian);  // NULL resource name
650
0
    if (out.write(buf, 2) != 2)
651
0
      throw Error(ErrorCode::kerImageWriteFailed);
652
0
    ul2Data(buf, static_cast<uint32_t>(xmpPacket.size()), bigEndian);
653
0
    if (out.write(buf, 4) != 4)
654
0
      throw Error(ErrorCode::kerImageWriteFailed);
655
    // Write XMPPacket
656
0
    if (out.write(reinterpret_cast<const byte*>(xmpPacket.data()), xmpPacket.size()) != xmpPacket.size())
657
0
      throw Error(ErrorCode::kerImageWriteFailed);
658
0
    if (out.error())
659
0
      throw Error(ErrorCode::kerImageWriteFailed);
660
0
    resLength += static_cast<uint32_t>(xmpPacket.size()) + 12;
661
0
    if (xmpPacket.size() & 1)  // even padding
662
0
    {
663
0
      buf[0] = 0;
664
0
      if (out.write(buf, 1) != 1)
665
0
        throw Error(ErrorCode::kerImageWriteFailed);
666
0
      resLength++;
667
0
    }
668
0
  }
669
0
  return resLength;
670
0
}  // PsdImage::writeXmpData
671
672
// *************************************************************************
673
// free functions
674
76
Image::UniquePtr newPsdInstance(BasicIo::UniquePtr io, bool /*create*/) {
675
76
  auto image = std::make_unique<PsdImage>(std::move(io));
676
76
  if (!image->good()) {
677
10
    return nullptr;
678
10
  }
679
66
  return image;
680
76
}
681
682
4.87k
bool isPsdType(BasicIo& iIo, bool advance) {
683
4.87k
  const int32_t len = 6;
684
4.87k
  const std::array<byte, len> PsdHeader{'8', 'B', 'P', 'S', 0, 1};
685
4.87k
  std::array<byte, len> buf;
686
4.87k
  iIo.read(buf.data(), len);
687
4.87k
  if (iIo.error() || iIo.eof()) {
688
245
    return false;
689
245
  }
690
4.62k
  bool matched = buf == PsdHeader;
691
4.62k
  if (!advance || !matched) {
692
4.62k
    iIo.seek(-len, BasicIo::cur);
693
4.62k
  }
694
695
4.62k
  return matched;
696
4.87k
}
697
}  // namespace Exiv2