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