1 // Copyright (c) the JPEG XL Project Authors. All rights reserved.
2 //
3 // Use of this source code is governed by a BSD-style
4 // license that can be found in the LICENSE file.
5 
6 #include "lib/extras/codec_psd.h"
7 
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 
12 #include <algorithm>
13 #include <numeric>
14 #include <string>
15 #include <utility>
16 #include <vector>
17 
18 #include "lib/jxl/base/bits.h"
19 #include "lib/jxl/base/byte_order.h"
20 #include "lib/jxl/base/compiler_specific.h"
21 #include "lib/jxl/base/file_io.h"
22 #include "lib/jxl/color_management.h"
23 #include "lib/jxl/common.h"
24 #include "lib/jxl/fields.h"  // AllDefault
25 #include "lib/jxl/image.h"
26 #include "lib/jxl/image_bundle.h"
27 #include "lib/jxl/image_ops.h"
28 #include "lib/jxl/luminance.h"
29 
30 namespace jxl {
31 namespace {
32 
get_be_int(int bytes,const uint8_t * & pos,const uint8_t * maxpos)33 uint64_t get_be_int(int bytes, const uint8_t*& pos, const uint8_t* maxpos) {
34   uint64_t r = 0;
35   if (pos + bytes <= maxpos) {
36     if (bytes == 1) {
37       r = *pos;
38     } else if (bytes == 2) {
39       r = LoadBE16(pos);
40     } else if (bytes == 4) {
41       r = LoadBE32(pos);
42     } else if (bytes == 8) {
43       r = LoadBE64(pos);
44     }
45   }
46   pos += bytes;
47   return r;
48 }
49 
50 // Copies up to n bytes, without reading from maxpos (the STL-style end).
safe_copy(const uint8_t * JXL_RESTRICT pos,const uint8_t * JXL_RESTRICT maxpos,char * JXL_RESTRICT out,size_t n)51 void safe_copy(const uint8_t* JXL_RESTRICT pos,
52                const uint8_t* JXL_RESTRICT maxpos, char* JXL_RESTRICT out,
53                size_t n) {
54   for (size_t i = 0; i < n; ++i) {
55     if (pos + i >= maxpos) return;
56     out[i] = pos[i];
57   }
58 }
59 
60 // maxpos is the STL-style end! The valid range is up to [pos, maxpos).
safe_strncmp(const uint8_t * pos,const uint8_t * maxpos,const char * s2,size_t n)61 int safe_strncmp(const uint8_t* pos, const uint8_t* maxpos, const char* s2,
62                  size_t n) {
63   if (pos + n > maxpos) return 1;
64   return strncmp((const char*)pos, s2, n);
65 }
66 constexpr int PSD_VERBOSITY = 1;
67 
decode_layer(const uint8_t * & pos,const uint8_t * maxpos,ImageBundle & layer,std::vector<int> chans,std::vector<bool> invert,int w,int h,int version,int colormodel,bool is_layer,int depth)68 Status decode_layer(const uint8_t*& pos, const uint8_t* maxpos,
69                     ImageBundle& layer, std::vector<int> chans,
70                     std::vector<bool> invert, int w, int h, int version,
71                     int colormodel, bool is_layer, int depth) {
72   int compression_method = 2;
73   int nb_channels = chans.size();
74   JXL_DEBUG_V(PSD_VERBOSITY,
75               "Trying to decode layer with dimensions %ix%i and %i channels", w,
76               h, nb_channels);
77   if (w <= 0 || h <= 0) return JXL_FAILURE("PSD: empty layer");
78   for (int c = 0; c < nb_channels; c++) {
79     // skip nop byte padding
80     while (pos < maxpos && *pos == 128) pos++;
81     JXL_DEBUG_V(PSD_VERBOSITY, "Channel %i (pos %zu)", c, (size_t)pos);
82     // Merged image stores all channels together (same compression method)
83     // Layers store channel per channel
84     if (is_layer || c == 0) {
85       compression_method = get_be_int(2, pos, maxpos);
86       JXL_DEBUG_V(PSD_VERBOSITY, "compression method: %i", compression_method);
87       if (compression_method > 1 || compression_method < 0) {
88         return JXL_FAILURE("PSD: can't handle compression method %i",
89                            compression_method);
90       }
91     }
92 
93     if (!is_layer && c < colormodel) {
94       // skip to the extra channels
95       if (compression_method == 0) {
96         pos += w * h * (depth >> 3) * colormodel;
97         c = colormodel - 1;
98         continue;
99       }
100       size_t skip_amount = 0;
101       for (int i = 0; i < nb_channels; i++) {
102         if (i < colormodel) {
103           for (int y = 0; y < h; y++) {
104             skip_amount += get_be_int(2 * version, pos, maxpos);
105           }
106         } else {
107           pos += h * 2 * version;
108         }
109       }
110       pos += skip_amount;
111       c = colormodel - 1;
112       continue;
113     }
114     if (is_layer || c == 0) {
115       // skip the line-counts, we don't need them
116       if (compression_method == 1) {
117         pos += h * (is_layer ? 1 : nb_channels) * 2 *
118                version;  // PSB uses 4 bytes per rowsize instead of 2
119       }
120     }
121     int c_id = chans[c];
122     if (c_id < 0) continue;  // skip
123     if (static_cast<unsigned int>(c_id) >= 3 + layer.extra_channels().size())
124       return JXL_FAILURE("PSD: can't handle channel id %i", c_id);
125     ImageF& ch = (c_id < 3 ? layer.color()->Plane(c_id)
126                            : layer.extra_channels()[c_id - 3]);
127 
128     for (int y = 0; y < h; y++) {
129       if (pos > maxpos) return JXL_FAILURE("PSD: premature end of input");
130       float* const JXL_RESTRICT row = ch.Row(y);
131       if (compression_method == 0) {
132         // uncompressed is easy
133         if (depth == 8) {
134           for (int x = 0; x < w; x++) {
135             row[x] = get_be_int(1, pos, maxpos) * (1.f / 255.f);
136           }
137         } else if (depth == 16) {
138           for (int x = 0; x < w; x++) {
139             row[x] = get_be_int(2, pos, maxpos) * (1.f / 65535.f);
140           }
141         } else if (depth == 32) {
142           for (int x = 0; x < w; x++) {
143             uint32_t f = get_be_int(4, pos, maxpos);
144             memcpy(&row[x], &f, 4);
145           }
146         }
147       } else {
148         // RLE is not that hard
149         if (depth != 8)
150           return JXL_FAILURE("PSD: did not expect RLE with depth>1");
151         for (int x = 0; x < w;) {
152           if (pos >= maxpos) return JXL_FAILURE("PSD: out of bounds");
153           int8_t rle = *pos++;
154           if (rle <= 0) {
155             if (rle == -128) continue;  // nop
156             int count = 1 - rle;
157             float v = get_be_int(1, pos, maxpos) * (1.f / 255.f);
158             while (count && x < w) {
159               row[x] = v;
160               count--;
161               x++;
162             }
163             if (count) return JXL_FAILURE("PSD: row overflow");
164           } else {
165             int count = 1 + rle;
166             while (count && x < w) {
167               row[x] = get_be_int(1, pos, maxpos) * (1.f / 255.f);
168               count--;
169               x++;
170             }
171             if (count) return JXL_FAILURE("PSD: row overflow");
172           }
173         }
174       }
175       if (invert[c]) {
176         // sometimes 0 means full ink
177         for (int x = 0; x < w; x++) {
178           row[x] = 1.f - row[x];
179         }
180       }
181     }
182     JXL_DEBUG_V(PSD_VERBOSITY, "Channel %i read.", c);
183   }
184 
185   return true;
186 }
187 
188 }  // namespace
189 
DecodeImagePSD(const Span<const uint8_t> bytes,ThreadPool * pool,CodecInOut * io)190 Status DecodeImagePSD(const Span<const uint8_t> bytes, ThreadPool* pool,
191                       CodecInOut* io) {
192   const uint8_t* pos = bytes.data();
193   const uint8_t* maxpos = bytes.data() + bytes.size();
194   if (safe_strncmp(pos, maxpos, "8BPS", 4)) return false;  // not a PSD file
195   JXL_DEBUG_V(PSD_VERBOSITY, "trying psd decode");
196   pos += 4;
197   int version = get_be_int(2, pos, maxpos);
198   JXL_DEBUG_V(PSD_VERBOSITY, "Version=%i", version);
199   if (version < 1 || version > 2)
200     return JXL_FAILURE("PSD: unknown format version");
201   // PSD = version 1, PSB = version 2
202   pos += 6;
203   int nb_channels = get_be_int(2, pos, maxpos);
204   size_t ysize = get_be_int(4, pos, maxpos);
205   size_t xsize = get_be_int(4, pos, maxpos);
206   const SizeConstraints* constraints = &io->constraints;
207   JXL_RETURN_IF_ERROR(VerifyDimensions(constraints, xsize, ysize));
208   uint64_t total_pixel_count = static_cast<uint64_t>(xsize) * ysize;
209   int bitdepth = get_be_int(2, pos, maxpos);
210   if (bitdepth != 8 && bitdepth != 16 && bitdepth != 32) {
211     return JXL_FAILURE("PSD: bit depth %i invalid or not supported", bitdepth);
212   }
213   if (bitdepth == 32) {
214     io->metadata.m.SetFloat32Samples();
215   } else {
216     io->metadata.m.SetUintSamples(bitdepth);
217   }
218   int colormodel = get_be_int(2, pos, maxpos);
219   // 1 = Grayscale, 3 = RGB, 4 = CMYK
220   if (colormodel != 1 && colormodel != 3 && colormodel != 4)
221     return JXL_FAILURE("PSD: unsupported color model");
222 
223   int real_nb_channels = colormodel;
224   std::vector<std::vector<float>> spotcolor;
225 
226   if (get_be_int(4, pos, maxpos))
227     return JXL_FAILURE("PSD: Unsupported color mode section");
228 
229   bool hasmergeddata = true;
230   bool have_alpha = false;
231   bool merged_has_alpha = false;
232   size_t metalength = get_be_int(4, pos, maxpos);
233   const uint8_t* metaoffset = pos;
234   while (pos < metaoffset + metalength) {
235     char header[5] = "????";
236     safe_copy(pos, maxpos, header, 4);
237     if (memcmp(header, "8BIM", 4) != 0) {
238       return JXL_FAILURE("PSD: Unexpected image resource header: %s", header);
239     }
240     pos += 4;
241     int id = get_be_int(2, pos, maxpos);
242     int namelength = get_be_int(1, pos, maxpos);
243     pos += namelength;
244     if (!(namelength & 1)) pos++;  // padding to even length
245     size_t blocklength = get_be_int(4, pos, maxpos);
246     // JXL_DEBUG_V(PSD_VERBOSITY, "block id: %i | block length: %zu",id,
247     // blocklength);
248     if (pos > maxpos) return JXL_FAILURE("PSD: Unexpected end of file");
249     if (id == 1039) {  // ICC profile
250       size_t delta = maxpos - pos;
251       if (delta < blocklength) {
252         return JXL_FAILURE("PSD: Invalid block length");
253       }
254       PaddedBytes icc;
255       icc.resize(blocklength);
256       memcpy(icc.data(), pos, blocklength);
257       if (!io->metadata.m.color_encoding.SetICC(std::move(icc))) {
258         return JXL_FAILURE("PSD: Invalid color profile");
259       }
260     } else if (id == 1057) {  // compatibility mode or not?
261       if (get_be_int(4, pos, maxpos) != 1) {
262         return JXL_FAILURE("PSD: expected version=1 in id=1057 resource block");
263       }
264       hasmergeddata = get_be_int(1, pos, maxpos);
265       pos++;
266       blocklength -= 6;       // already skipped these bytes
267     } else if (id == 1077) {  // spot colors
268       int version = get_be_int(4, pos, maxpos);
269       if (version != 1) {
270         return JXL_FAILURE(
271             "PSD: expected DisplayInfo version 1, got version %i", version);
272       }
273       int spotcolorcount = nb_channels - colormodel;
274       JXL_DEBUG_V(PSD_VERBOSITY, "Reading %i spot colors. %zu", spotcolorcount,
275                   blocklength);
276       for (int k = 0; k < spotcolorcount; k++) {
277         int colorspace = get_be_int(2, pos, maxpos);
278         if ((colormodel == 3 && colorspace != 0) ||
279             (colormodel == 4 && colorspace != 2)) {
280           return JXL_FAILURE(
281               "PSD: cannot handle spot colors in different color spaces than "
282               "image itself");
283         }
284         if (colorspace == 2) JXL_WARNING("PSD: K ignored in CMYK spot color");
285         std::vector<float> color;
286         color.push_back(get_be_int(2, pos, maxpos) / 65535.f);  // R or C
287         color.push_back(get_be_int(2, pos, maxpos) / 65535.f);  // G or M
288         color.push_back(get_be_int(2, pos, maxpos) / 65535.f);  // B or Y
289         color.push_back(get_be_int(2, pos, maxpos) / 65535.f);  // ignored or K
290         color.push_back(get_be_int(2, pos, maxpos) /
291                         100.f);  // solidity (alpha, basically)
292         int kind = get_be_int(1, pos, maxpos);
293         JXL_DEBUG_V(PSD_VERBOSITY, "Kind=%i", kind);
294         color.push_back(kind);
295         spotcolor.push_back(color);
296         if (kind == 2) {
297           JXL_DEBUG_V(PSD_VERBOSITY, "Actual spot color");
298         } else if (kind == 1) {
299           JXL_DEBUG_V(PSD_VERBOSITY, "Mask (alpha) channel");
300         } else if (kind == 0) {
301           JXL_DEBUG_V(PSD_VERBOSITY, "Selection (alpha) channel");
302         } else {
303           return JXL_FAILURE("PSD: Unknown extra channel type");
304         }
305       }
306       if (blocklength & 1) pos++;
307       blocklength = 0;
308     }
309     pos += blocklength;
310     if (blocklength & 1) pos++;  // padding again
311   }
312 
313   size_t layerlength = get_be_int(4 * version, pos, maxpos);
314   const uint8_t* after_layers_pos = pos + layerlength;
315   if (after_layers_pos < pos) return JXL_FAILURE("PSD: invalid layer length");
316   if (layerlength) {
317     pos += 4 * version;  // don't care about layerinfolength
318     JXL_DEBUG_V(PSD_VERBOSITY, "Layer section length: %zu", layerlength);
319     int layercount = static_cast<int16_t>(get_be_int(2, pos, maxpos));
320     JXL_DEBUG_V(PSD_VERBOSITY, "Layer count: %i", layercount);
321     io->frames.clear();
322 
323     if (layercount == 0) {
324       if (get_be_int(2, pos, maxpos) != 0) {
325         return JXL_FAILURE(
326             "PSD: Expected zero padding before additional layer info");
327       }
328       while (pos < after_layers_pos) {
329         if (safe_strncmp(pos, maxpos, "8BIM", 4) &&
330             safe_strncmp(pos, maxpos, "8B64", 4))
331           return JXL_FAILURE("PSD: Unexpected layer info signature");
332         pos += 4;
333         const uint8_t* tpos = pos;
334         pos += 4;
335         size_t blocklength = get_be_int(4 * version, pos, maxpos);
336         JXL_DEBUG_V(PSD_VERBOSITY, "Length=%zu", blocklength);
337         if (blocklength > 0) {
338           if (pos >= maxpos) return JXL_FAILURE("PSD: Unexpected end of file");
339           size_t delta = maxpos - pos;
340           if (delta < blocklength) {
341             return JXL_FAILURE("PSD: Invalid block length");
342           }
343         }
344         if (!safe_strncmp(tpos, maxpos, "Layr", 4) ||
345             !safe_strncmp(tpos, maxpos, "Lr16", 4) ||
346             !safe_strncmp(tpos, maxpos, "Lr32", 4)) {
347           layercount = static_cast<int16_t>(get_be_int(2, pos, maxpos));
348           if (layercount < 0) {
349             return JXL_FAILURE("PSD: Invalid layer count");
350           }
351           JXL_DEBUG_V(PSD_VERBOSITY, "Real layer count: %i", layercount);
352           break;
353         }
354         if (!safe_strncmp(tpos, maxpos, "Mtrn", 4) ||
355             !safe_strncmp(tpos, maxpos, "Mt16", 4) ||
356             !safe_strncmp(tpos, maxpos, "Mt32", 4)) {
357           JXL_DEBUG_V(PSD_VERBOSITY, "Merged layer has transparency channel");
358           if (nb_channels > real_nb_channels) {
359             real_nb_channels++;
360             have_alpha = true;
361             merged_has_alpha = true;
362           }
363         }
364         pos += blocklength;
365       }
366     } else if (layercount < 0) {
367       // negative layer count indicates merged has alpha and it is to be shown
368       if (nb_channels > real_nb_channels) {
369         real_nb_channels++;
370         have_alpha = true;
371         merged_has_alpha = true;
372       }
373       layercount = -layercount;
374     } else {
375       // multiple layers implies there is alpha
376       real_nb_channels++;
377       have_alpha = true;
378     }
379 
380     ExtraChannelInfo info;
381     info.bit_depth.bits_per_sample = bitdepth;
382     info.dim_shift = 0;
383 
384     if (colormodel == 4) {  // cmyk
385       info.type = ExtraChannel::kBlack;
386       io->metadata.m.extra_channel_info.push_back(info);
387     }
388     if (have_alpha) {
389       JXL_DEBUG_V(PSD_VERBOSITY, "Have alpha");
390       info.type = ExtraChannel::kAlpha;
391       info.alpha_associated =
392           false;  // true? PSD is not consistent with this, need to check
393       io->metadata.m.extra_channel_info.push_back(info);
394     }
395     if (merged_has_alpha && !spotcolor.empty() && spotcolor[0][5] == 1) {
396       // first alpha channel
397       spotcolor.erase(spotcolor.begin());
398     }
399     for (size_t i = 0; i < spotcolor.size(); i++) {
400       real_nb_channels++;
401       if (spotcolor[i][5] == 2) {
402         info.type = ExtraChannel::kSpotColor;
403         info.spot_color[0] = spotcolor[i][0];
404         info.spot_color[1] = spotcolor[i][1];
405         info.spot_color[2] = spotcolor[i][2];
406         info.spot_color[3] = spotcolor[i][4];
407       } else if (spotcolor[i][5] == 1) {
408         info.type = ExtraChannel::kAlpha;
409       } else if (spotcolor[i][5] == 0) {
410         info.type = ExtraChannel::kSelectionMask;
411       } else
412         return JXL_FAILURE("PSD: unhandled extra channel");
413       io->metadata.m.extra_channel_info.push_back(info);
414     }
415     std::vector<std::vector<int>> layer_chan_id;
416     std::vector<size_t> layer_offsets(layercount + 1, 0);
417     std::vector<bool> is_real_layer(layercount, false);
418     for (int l = 0; l < layercount; l++) {
419       ImageBundle layer(&io->metadata.m);
420       layer.duration = 0;
421       layer.blend = (l > 0);
422 
423       layer.use_for_next_frame = (l + 1 < layercount);
424       layer.origin.y0 = get_be_int(4, pos, maxpos);
425       layer.origin.x0 = get_be_int(4, pos, maxpos);
426       size_t height = get_be_int(4, pos, maxpos) - layer.origin.y0;
427       size_t width = get_be_int(4, pos, maxpos) - layer.origin.x0;
428       JXL_DEBUG_V(PSD_VERBOSITY, "Layer %i: %zu x %zu at origin (%i, %i)", l,
429                   width, height, layer.origin.x0, layer.origin.y0);
430       int nb_chs = get_be_int(2, pos, maxpos);
431       JXL_DEBUG_V(PSD_VERBOSITY, "  channels: %i", nb_chs);
432       std::vector<int> chan_ids;
433       layer_offsets[l + 1] = layer_offsets[l];
434       for (int lc = 0; lc < nb_chs; lc++) {
435         int id = get_be_int(2, pos, maxpos);
436         JXL_DEBUG_V(PSD_VERBOSITY, "    id=%i", id);
437         if (id == 65535) {
438           chan_ids.push_back(colormodel);  // alpha
439         } else if (id == 65534) {
440           chan_ids.push_back(-1);  // layer mask, ignored
441         } else {
442           chan_ids.push_back(id);  // color channel
443         }
444         layer_offsets[l + 1] += get_be_int(4 * version, pos, maxpos);
445       }
446       layer_chan_id.push_back(chan_ids);
447       if (safe_strncmp(pos, maxpos, "8BIM", 4))
448         return JXL_FAILURE("PSD: Layer %i: Unexpected signature (not 8BIM)", l);
449       pos += 4;
450       if (safe_strncmp(pos, maxpos, "norm", 4)) {
451         return JXL_FAILURE(
452             "PSD: Layer %i: Cannot handle non-default blend mode", l);
453       }
454       pos += 4;
455       int opacity = get_be_int(1, pos, maxpos);
456       if (opacity < 100) {
457         JXL_WARNING(
458             "PSD: ignoring opacity of semi-transparent layer %i (opacity=%i)",
459             l, opacity);
460       }
461       pos++;  // clipping
462       int flags = get_be_int(1, pos, maxpos);
463       pos++;
464       bool invisible = (flags & 2);
465       if (invisible) {
466         if (l + 1 < layercount) {
467           layer.blend = false;
468           layer.use_for_next_frame = false;
469         } else {
470           // TODO: instead add dummy last frame?
471           JXL_WARNING("PSD: invisible top layer was made visible");
472         }
473       }
474       size_t extradata = get_be_int(4, pos, maxpos);
475       JXL_DEBUG_V(PSD_VERBOSITY, "  extradata: %zu bytes", extradata);
476       const uint8_t* after_extra = pos + extradata;
477       // TODO: deal with non-empty layer masks
478       pos += get_be_int(4, pos, maxpos);  // skip layer mask data
479       pos += get_be_int(4, pos, maxpos);  // skip layer blend range data
480       size_t namelength = get_be_int(1, pos, maxpos);
481       size_t delta = maxpos - pos;
482       if (delta < namelength) return JXL_FAILURE("PSD: Invalid block length");
483       char lname[256] = {};
484       memcpy(lname, pos, namelength);
485       lname[namelength] = 0;
486       JXL_DEBUG_V(PSD_VERBOSITY, "  name: %s", lname);
487       pos = after_extra;
488       if (width == 0 || height == 0) {
489         JXL_DEBUG_V(PSD_VERBOSITY,
490                     "  NOT A REAL LAYER");  // probably layer group
491         continue;
492       }
493       is_real_layer[l] = true;
494       JXL_RETURN_IF_ERROR(VerifyDimensions(constraints, width, height));
495       uint64_t pixel_count = static_cast<uint64_t>(width) * height;
496       if (!SafeAdd(total_pixel_count, pixel_count, total_pixel_count)) {
497         return JXL_FAILURE("Image too big");
498       }
499       if (total_pixel_count > constraints->dec_max_pixels) {
500         return JXL_FAILURE("Image too big");
501       }
502       Image3F rgb(width, height);
503       layer.SetFromImage(std::move(rgb), io->metadata.m.color_encoding);
504       std::vector<ImageF> ec;
505       for (const auto& ec_meta : layer.metadata()->extra_channel_info) {
506         ImageF extra(width, height);
507         if (ec_meta.type == ExtraChannel::kAlpha) {
508           FillPlane(1.0f, &extra, Rect(extra));  // opaque
509         } else {
510           ZeroFillPlane(&extra, Rect(extra));  // zeroes
511         }
512         ec.push_back(std::move(extra));
513       }
514       if (!ec.empty()) layer.SetExtraChannels(std::move(ec));
515       layer.name = lname;
516       io->dec_pixels += layer.xsize() * layer.ysize();
517       io->frames.push_back(std::move(layer));
518     }
519 
520     std::vector<bool> invert(real_nb_channels, false);
521     int il = 0;
522     const uint8_t* bpos = pos;
523     for (int l = 0; l < layercount; l++) {
524       if (!is_real_layer[l]) continue;
525       pos = bpos + layer_offsets[l];
526       if (pos < bpos) return JXL_FAILURE("PSD: invalid layer offset");
527       JXL_DEBUG_V(PSD_VERBOSITY, "At position %i (%zu)",
528                   (int)(pos - bytes.data()), (size_t)pos);
529       ImageBundle& layer = io->frames[il++];
530       JXL_RETURN_IF_ERROR(decode_layer(pos, maxpos, layer, layer_chan_id[l],
531                                        invert, layer.xsize(), layer.ysize(),
532                                        version, colormodel, true, bitdepth));
533     }
534   } else
535     return JXL_FAILURE("PSD: no layer data found");
536 
537   if (!hasmergeddata && !spotcolor.empty()) {
538     return JXL_FAILURE("PSD: extra channel data declared but not found");
539   }
540 
541   if (!spotcolor.empty() || (hasmergeddata && io->frames.empty())) {
542     // PSD only has spot colors / extra alpha/mask data in the merged image
543     // We don't redundantly store the merged image, so we put it in the first
544     // layer (the next layers will kAdd zeroes to it)
545     pos = after_layers_pos;
546     bool have_only_merged = false;
547     if (io->frames.empty()) {
548       // There is only the merged image, no layers
549       ImageBundle nlayer(&io->metadata.m);
550       Image3F rgb(xsize, ysize);
551       nlayer.SetFromImage(std::move(rgb), io->metadata.m.color_encoding);
552       std::vector<ImageF> ec;
553       for (const auto& ec_meta : nlayer.metadata()->extra_channel_info) {
554         ImageF extra(xsize, ysize);
555         if (ec_meta.type == ExtraChannel::kAlpha) {
556           FillPlane(1.0f, &extra, Rect(extra));  // opaque
557         } else {
558           ZeroFillPlane(&extra, Rect(extra));  // zeroes
559         }
560         ec.push_back(std::move(extra));
561       }
562       if (!ec.empty()) nlayer.SetExtraChannels(std::move(ec));
563       io->dec_pixels += nlayer.xsize() * nlayer.ysize();
564       io->frames.push_back(std::move(nlayer));
565       have_only_merged = true;
566     }
567     ImageBundle& layer = io->frames[0];
568     std::vector<int> chan_id(real_nb_channels);
569     std::iota(chan_id.begin(), chan_id.end(), 0);
570     std::vector<bool> invert(real_nb_channels, false);
571     if (static_cast<int>(spotcolor.size()) + colormodel + 1 <
572         real_nb_channels) {
573       return JXL_FAILURE("Inconsistent layer configuration");
574     }
575     if (!merged_has_alpha) {
576       if (colormodel <= real_nb_channels) {
577         return JXL_FAILURE("Inconsistent layer configuration");
578       }
579       chan_id.erase(chan_id.begin() + colormodel);
580       invert.erase(invert.begin() + colormodel);
581     } else {
582       colormodel++;
583     }
584     for (size_t i = colormodel; i < invert.size(); i++) {
585       if (spotcolor[i - colormodel][5] == 2) invert[i] = true;
586       if (spotcolor[i - colormodel][5] == 0) invert[i] = true;
587     }
588     JXL_RETURN_IF_ERROR(decode_layer(
589         pos, maxpos, layer, chan_id, invert, layer.xsize(), layer.ysize(),
590         version, (have_only_merged ? 0 : colormodel), false, bitdepth));
591   }
592 
593   if (io->frames.empty()) return JXL_FAILURE("PSD: no layers");
594 
595   io->SetSize(xsize, ysize);
596 
597   SetIntensityTarget(io);
598 
599   return true;
600 }
601 
EncodeImagePSD(const CodecInOut * io,const ColorEncoding & c_desired,size_t bits_per_sample,ThreadPool * pool,PaddedBytes * bytes)602 Status EncodeImagePSD(const CodecInOut* io, const ColorEncoding& c_desired,
603                       size_t bits_per_sample, ThreadPool* pool,
604                       PaddedBytes* bytes) {
605   return JXL_FAILURE("PSD encoding not yet implemented");
606 }
607 
608 }  // namespace jxl
609