Coverage Report

Created: 2026-01-25 07:18

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
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