1 /*
2 ** SPDX-License-Identifier: BSD-3-Clause
3 ** Copyright Contributors to the OpenEXR Project.
4 */
5 
6 #include "openexr_chunkio.h"
7 
8 #include "internal_coding.h"
9 #include "internal_structs.h"
10 #include "internal_xdr.h"
11 
12 #include <limits.h>
13 #include <string.h>
14 
15 /**************************************/
16 
17 /* for testing, we include a bunch of internal stuff into the unit tests which are in c++ */
18 #if defined __has_include
19 #    if __has_include(<stdatomic.h>)
20 #        define EXR_HAS_STD_ATOMICS 1
21 #    endif
22 #endif
23 
24 #ifdef EXR_HAS_STD_ATOMICS
25 #    include <stdatomic.h>
26 #elif defined(_MSC_VER)
27 
28 /* msvc w/ c11 support is only very new, until we know what the preprocessor checks are, provide defaults */
29 #    include <windows.h>
30 
31 #    define atomic_load(object) InterlockedOr64 ((int64_t volatile*) object, 0)
32 
33 static inline int
atomic_compare_exchange_strong(uint64_t volatile * object,uint64_t * expected,uint64_t desired)34 atomic_compare_exchange_strong (
35     uint64_t volatile* object, uint64_t* expected, uint64_t desired)
36 {
37     uint64_t prev =
38         (uint64_t) InterlockedCompareExchange64 (object, desired, *expected);
39     if (prev == *expected) return 1;
40     *expected = prev;
41     return 0;
42 }
43 
44 #else
45 #    error OS unimplemented support for atomics
46 #endif
47 
48 /**************************************/
49 
50 static exr_result_t
extract_chunk_table(const struct _internal_exr_context * ctxt,const struct _internal_exr_part * part,uint64_t ** chunktable,uint64_t * chunkminoffset)51 extract_chunk_table (
52     const struct _internal_exr_context* ctxt,
53     const struct _internal_exr_part*    part,
54     uint64_t**                          chunktable,
55     uint64_t*                           chunkminoffset)
56 {
57     uint64_t* ctable     = NULL;
58     uint64_t  chunkoff   = part->chunk_table_offset;
59     uint64_t  chunkbytes = sizeof (uint64_t) * (uint64_t) part->chunk_count;
60 
61     *chunkminoffset = chunkoff + chunkbytes;
62 
63     ctable = (uint64_t*) atomic_load (
64         EXR_CONST_CAST (atomic_uintptr_t*, &(part->chunk_table)));
65     if (ctable == NULL)
66     {
67         int64_t   nread = 0;
68         uintptr_t eptr = 0, nptr = 0;
69 
70         exr_result_t rv;
71 
72         if (part->chunk_count <= 0)
73             return ctxt->report_error (
74                 ctxt, EXR_ERR_INVALID_ARGUMENT, "Invalid file with no chunks");
75 
76         if (ctxt->file_size > 0 &&
77             chunkbytes + chunkoff > (uint64_t) ctxt->file_size)
78             return ctxt->print_error (
79                 ctxt,
80                 EXR_ERR_INVALID_ARGUMENT,
81                 "chunk table size (%" PRIu64 ") too big for file size (%" PRId64
82                 ")",
83                 chunkbytes,
84                 ctxt->file_size);
85 
86         ctable = (uint64_t*) ctxt->alloc_fn (chunkbytes);
87         if (ctable == NULL)
88             return ctxt->standard_error (ctxt, EXR_ERR_OUT_OF_MEMORY);
89 
90         rv = ctxt->do_read (
91             ctxt, ctable, chunkbytes, &chunkoff, &nread, EXR_MUST_READ_ALL);
92         if (rv != EXR_ERR_SUCCESS)
93         {
94             ctxt->free_fn (ctable);
95             return rv;
96         }
97         priv_to_native64 (ctable, part->chunk_count);
98 
99         //EXR_GETFILE(f)->report_error( ctxt, EXR_ERR_UNKNOWN, "TODO: implement reconstructLineOffsets and similar" );
100         nptr = (uintptr_t) ctable;
101         // see if we win or not
102         if (!atomic_compare_exchange_strong (
103                 EXR_CONST_CAST (atomic_uintptr_t*, &(part->chunk_table)),
104                 &eptr,
105                 nptr))
106         {
107             ctxt->free_fn (ctable);
108             ctable = (uint64_t*) eptr;
109             if (ctable == NULL)
110                 return ctxt->standard_error (ctxt, EXR_ERR_OUT_OF_MEMORY);
111         }
112     }
113 
114     *chunktable = ctable;
115     return EXR_ERR_SUCCESS;
116 }
117 
118 /**************************************/
119 
120 static exr_result_t
alloc_chunk_table(const struct _internal_exr_context * ctxt,const struct _internal_exr_part * part,uint64_t ** chunktable)121 alloc_chunk_table (
122     const struct _internal_exr_context* ctxt,
123     const struct _internal_exr_part*    part,
124     uint64_t**                          chunktable)
125 {
126     uint64_t* ctable = NULL;
127 
128     /* we have the lock, but to access the type, we'll use the atomic function anyway */
129     ctable = (uint64_t*) atomic_load (
130         EXR_CONST_CAST (atomic_uintptr_t*, &(part->chunk_table)));
131     if (ctable == NULL)
132     {
133         uint64_t  chunkbytes = sizeof (uint64_t) * (uint64_t) part->chunk_count;
134         uintptr_t eptr = 0, nptr = 0;
135 
136         ctable = (uint64_t*) ctxt->alloc_fn (chunkbytes);
137         if (ctable == NULL)
138             return ctxt->standard_error (ctxt, EXR_ERR_OUT_OF_MEMORY);
139         memset (ctable, 0, chunkbytes);
140 
141         nptr = (uintptr_t) ctable;
142         if (!atomic_compare_exchange_strong (
143                 EXR_CONST_CAST (atomic_uintptr_t*, &(part->chunk_table)),
144                 &eptr,
145                 nptr))
146         {
147             ctxt->free_fn (ctable);
148             ctable = (uint64_t*) eptr;
149             if (ctable == NULL)
150                 return ctxt->standard_error (ctxt, EXR_ERR_OUT_OF_MEMORY);
151         }
152     }
153     *chunktable = ctable;
154     return EXR_ERR_SUCCESS;
155 }
156 
157 /**************************************/
158 
159 static uint64_t
compute_chunk_unpack_size(int y,int width,int height,int lpc,const struct _internal_exr_part * part)160 compute_chunk_unpack_size (
161     int                              y,
162     int                              width,
163     int                              height,
164     int                              lpc,
165     const struct _internal_exr_part* part)
166 {
167     uint64_t unpacksize = 0;
168     if (part->chan_has_line_sampling || height != lpc)
169     {
170         const exr_attr_chlist_t* chanlist = part->channels->chlist;
171         for (int c = 0; c < chanlist->num_channels; ++c)
172         {
173             const exr_attr_chlist_entry_t* curc = (chanlist->entries + c);
174             uint64_t chansz = ((curc->pixel_type == EXR_PIXEL_HALF) ? 2 : 4);
175             chansz *= ((uint64_t) width);
176             if (curc->x_sampling > 1) chansz /= ((uint64_t) curc->x_sampling);
177             chansz *= ((uint64_t) height);
178             if (curc->y_sampling > 1)
179             {
180                 if (height > 1)
181                 {
182                     if (curc->y_sampling > height)
183                         chansz /= ((uint64_t) height);
184                     else
185                         chansz /= ((uint64_t) curc->y_sampling);
186                 }
187                 else if ((y % ((int) curc->y_sampling)) != 0)
188                     chansz = 0;
189             }
190             unpacksize += chansz;
191         }
192     }
193     else
194         unpacksize = part->unpacked_size_per_chunk;
195     return unpacksize;
196 }
197 
198 /**************************************/
199 
200 exr_result_t
exr_read_scanline_chunk_info(exr_const_context_t ctxt,int part_index,int y,exr_chunk_info_t * cinfo)201 exr_read_scanline_chunk_info (
202     exr_const_context_t ctxt, int part_index, int y, exr_chunk_info_t* cinfo)
203 {
204     exr_result_t     rv;
205     int              miny, cidx, rdcnt, lpc;
206     int32_t          data[3];
207     int64_t          ddata[3];
208     int64_t          fsize;
209     uint64_t         chunkmin, dataoff;
210     exr_attr_box2i_t dw;
211     uint64_t*        ctable;
212     EXR_PROMOTE_READ_CONST_CONTEXT_AND_PART_OR_ERROR (ctxt, part_index);
213 
214     if (!cinfo) return pctxt->standard_error (pctxt, EXR_ERR_INVALID_ARGUMENT);
215 
216     if (part->storage_mode == EXR_STORAGE_TILED ||
217         part->storage_mode == EXR_STORAGE_DEEP_TILED)
218     {
219         return pctxt->standard_error (pctxt, EXR_ERR_SCAN_TILE_MIXEDAPI);
220     }
221 
222     dw = part->data_window;
223     if (y < dw.min.y || y > dw.max.y)
224     {
225         return pctxt->print_error (
226             pctxt,
227             EXR_ERR_INVALID_ARGUMENT,
228             "Invalid request for scanline %d outside range of data window (%d - %d)",
229             y,
230             dw.min.y,
231             dw.max.y);
232     }
233 
234     lpc  = part->lines_per_chunk;
235     cidx = (y - dw.min.y);
236     if (lpc > 1) cidx /= lpc;
237 
238     // do we need to invert this when reading decreasing y? it appears not
239     //if (part->lineorder == EXR_LINEORDER_DECREASING_Y)
240     //    cidx = part->chunk_count - (cidx + 1);
241     miny = (dw.min.y + cidx * lpc);
242 
243     if (cidx < 0 || cidx >= part->chunk_count)
244     {
245         return pctxt->print_error (
246             pctxt,
247             EXR_ERR_INVALID_ARGUMENT,
248             "Invalid request for scanline %d in chunk %d outside chunk count %d",
249             y,
250             cidx,
251             part->chunk_count);
252     }
253 
254     cinfo->idx         = cidx;
255     cinfo->type        = (uint8_t) part->storage_mode;
256     cinfo->compression = (uint8_t) part->comp_type;
257     cinfo->start_x     = dw.min.x;
258     cinfo->start_y     = miny;
259     cinfo->width       = dw.max.x - dw.min.x + 1;
260     cinfo->height      = lpc;
261     if (miny < dw.min.y)
262     {
263         cinfo->start_y = dw.min.y;
264         cinfo->height -= (dw.min.y - miny);
265     }
266     else if ((miny + lpc) > dw.max.y)
267     {
268         cinfo->height = (dw.max.y - miny + 1);
269     }
270     cinfo->level_x = 0;
271     cinfo->level_y = 0;
272 
273     /* need to read from the file to get the packed chunk size */
274     rv = extract_chunk_table (pctxt, part, &ctable, &chunkmin);
275     if (rv != EXR_ERR_SUCCESS) return rv;
276 
277     fsize = pctxt->file_size;
278 
279     dataoff = ctable[cidx];
280     if (dataoff < chunkmin || (fsize > 0 && dataoff > (uint64_t) fsize))
281     {
282         return pctxt->print_error (
283             pctxt,
284             EXR_ERR_BAD_CHUNK_LEADER,
285             "Corrupt chunk offset table: scanline %d, chunk index %d recorded at file offset %" PRIu64,
286             y,
287             cidx,
288             dataoff);
289     }
290 
291     /* multi part files have the part for validation */
292     rdcnt = (pctxt->is_multipart) ? 2 : 1;
293     /* deep has 64-bit data, so be variable about what we read */
294     if (part->storage_mode != EXR_STORAGE_DEEP_SCANLINE) ++rdcnt;
295 
296     rv = pctxt->do_read (
297         pctxt,
298         data,
299         (size_t) (rdcnt) * sizeof (int32_t),
300         &dataoff,
301         NULL,
302         EXR_MUST_READ_ALL);
303 
304     if (rv != EXR_ERR_SUCCESS) return rv;
305 
306     priv_to_native32 (data, rdcnt);
307 
308     rdcnt = 0;
309     if (pctxt->is_multipart)
310     {
311         if (data[rdcnt] != part_index)
312         {
313             return pctxt->print_error (
314                 pctxt,
315                 EXR_ERR_BAD_CHUNK_LEADER,
316                 "Preparing read scanline %d (chunk %d), found corrupt leader: part says %d, expected %d",
317                 y,
318                 cidx,
319                 data[rdcnt],
320                 part_index);
321         }
322         ++rdcnt;
323     }
324     if (miny != data[rdcnt])
325     {
326         return pctxt->print_error (
327             pctxt,
328             EXR_ERR_BAD_CHUNK_LEADER,
329             "Preparing to read scanline %d (chunk %d), found corrupt leader: scanline says %d, expected %d",
330             y,
331             cidx,
332             data[rdcnt],
333             miny);
334     }
335 
336     if (part->storage_mode == EXR_STORAGE_DEEP_SCANLINE)
337     {
338         rv = pctxt->do_read (
339             pctxt,
340             ddata,
341             3 * sizeof (int64_t),
342             &dataoff,
343             NULL,
344             EXR_MUST_READ_ALL);
345         if (rv != EXR_ERR_SUCCESS) { return rv; }
346         priv_to_native64 (ddata, 3);
347 
348         if (ddata[0] < 0)
349         {
350             return pctxt->print_error (
351                 pctxt,
352                 EXR_ERR_BAD_CHUNK_LEADER,
353                 "Preparing to read scanline %d (chunk %d), found corrupt leader: invalid sample table size %" PRId64,
354                 y,
355                 cidx,
356                 ddata[0]);
357         }
358         if (ddata[1] < 0 || ddata[1] > (int64_t) INT_MAX)
359         {
360             return pctxt->print_error (
361                 pctxt,
362                 EXR_ERR_BAD_CHUNK_LEADER,
363                 "Preparing to read scanline %d (chunk %d), found corrupt leader: invalid packed data size %" PRId64,
364                 y,
365                 cidx,
366                 ddata[1]);
367         }
368         if (ddata[2] < 0 || ddata[2] > (int64_t) INT_MAX)
369         {
370             return pctxt->print_error (
371                 pctxt,
372                 EXR_ERR_BAD_CHUNK_LEADER,
373                 "Preparing to scanline %d (chunk %d), found corrupt leader: unsupported unpacked data size %" PRId64,
374                 y,
375                 cidx,
376                 ddata[2]);
377         }
378 
379         cinfo->sample_count_data_offset = dataoff;
380         cinfo->sample_count_table_size  = (uint64_t) ddata[0];
381         cinfo->data_offset              = dataoff + (uint64_t) ddata[0];
382         cinfo->packed_size              = (uint64_t) ddata[1];
383         cinfo->unpacked_size            = (uint64_t) ddata[2];
384 
385         if (fsize > 0 &&
386             ((cinfo->sample_count_data_offset +
387               cinfo->sample_count_table_size) > ((uint64_t) fsize) ||
388              (cinfo->data_offset + cinfo->packed_size) > ((uint64_t) fsize)))
389         {
390             return pctxt->print_error (
391                 pctxt,
392                 EXR_ERR_BAD_CHUNK_LEADER,
393                 "Preparing to scanline %d (chunk %d), found corrupt leader: sample table and data result in access past end of the file: sample table size %" PRId64
394                 " + data size %" PRId64 " larger than file %" PRId64,
395                 y,
396                 cidx,
397                 ddata[0],
398                 ddata[1],
399                 fsize);
400         }
401     }
402     else
403     {
404         uint64_t unpacksize = compute_chunk_unpack_size (
405             y, cinfo->width, cinfo->height, lpc, part);
406 
407         ++rdcnt;
408         if (data[rdcnt] < 0 ||
409             (uint64_t) data[rdcnt] > part->unpacked_size_per_chunk)
410         {
411             return pctxt->print_error (
412                 pctxt,
413                 EXR_ERR_BAD_CHUNK_LEADER,
414                 "Preparing to read scanline %d (chunk %d), found corrupt leader: packed data size says %" PRIu64
415                 ", must be between 0 and %" PRIu64,
416                 y,
417                 cidx,
418                 (uint64_t) data[rdcnt],
419                 part->unpacked_size_per_chunk);
420         }
421 
422         cinfo->data_offset              = dataoff;
423         cinfo->packed_size              = (uint64_t) data[rdcnt];
424         cinfo->unpacked_size            = unpacksize;
425         cinfo->sample_count_data_offset = 0;
426         cinfo->sample_count_table_size  = 0;
427 
428         if (fsize > 0 &&
429             (cinfo->data_offset + cinfo->packed_size) > ((uint64_t) fsize))
430         {
431             return pctxt->print_error (
432                 pctxt,
433                 EXR_ERR_BAD_CHUNK_LEADER,
434                 "Preparing to read scanline %d (chunk %d), found corrupt leader: packed size %" PRIu64
435                 ", file size %" PRId64,
436                 y,
437                 cidx,
438                 (uint64_t) data[rdcnt],
439                 fsize);
440         }
441     }
442 
443     if (cinfo->packed_size == 0 && cinfo->unpacked_size > 0)
444         return pctxt->report_error (
445             pctxt,
446             EXR_ERR_INVALID_ARGUMENT,
447             "Invalid packed size of 0");
448     return EXR_ERR_SUCCESS;
449 }
450 
451 /**************************************/
452 
453 static exr_result_t
compute_tile_chunk_off(const struct _internal_exr_context * ctxt,const struct _internal_exr_part * part,int tilex,int tiley,int levelx,int levely,int32_t * chunkoffout)454 compute_tile_chunk_off (
455     const struct _internal_exr_context* ctxt,
456     const struct _internal_exr_part*    part,
457     int                                 tilex,
458     int                                 tiley,
459     int                                 levelx,
460     int                                 levely,
461     int32_t*                            chunkoffout)
462 {
463     int                        numx, numy;
464     int64_t                    chunkoff = 0;
465     const exr_attr_tiledesc_t* tiledesc = part->tiles->tiledesc;
466 
467     switch (EXR_GET_TILE_LEVEL_MODE ((*tiledesc)))
468     {
469         case EXR_TILE_ONE_LEVEL:
470         case EXR_TILE_MIPMAP_LEVELS:
471             if (levelx != levely)
472             {
473                 return ctxt->print_error (
474                     ctxt,
475                     EXR_ERR_INVALID_ARGUMENT,
476                     "Request for tile (%d, %d) level (%d, %d), but single level and mipmap tiles must have same level x and y",
477                     tilex,
478                     tiley,
479                     levelx,
480                     levely);
481             }
482             if (levelx >= part->num_tile_levels_x)
483             {
484                 return ctxt->print_error (
485                     ctxt,
486                     EXR_ERR_INVALID_ARGUMENT,
487                     "Request for tile (%d, %d) level %d, but level past available levels (%d)",
488                     tilex,
489                     tiley,
490                     levelx,
491                     part->num_tile_levels_x);
492             }
493 
494             numx = part->tile_level_tile_count_x[levelx];
495             numy = part->tile_level_tile_count_y[levelx];
496 
497             if (tilex >= numx || tiley >= numy)
498             {
499                 return ctxt->print_error (
500                     ctxt,
501                     EXR_ERR_INVALID_ARGUMENT,
502                     "Request for tile (%d, %d) level %d, but level only has %d x %d tiles",
503                     tilex,
504                     tiley,
505                     levelx,
506                     numx,
507                     numy);
508             }
509 
510             for (int l = 0; l < levelx; ++l)
511                 chunkoff +=
512                     ((int64_t) part->tile_level_tile_count_x[l] *
513                      (int64_t) part->tile_level_tile_count_y[l]);
514             chunkoff += tiley * numx + tilex;
515             break;
516 
517         case EXR_TILE_RIPMAP_LEVELS:
518             if (levelx >= part->num_tile_levels_x)
519             {
520                 return ctxt->print_error (
521                     ctxt,
522                     EXR_ERR_INVALID_ARGUMENT,
523                     "Request for tile (%d, %d) level %d, %d, but x level past available levels (%d)",
524                     tilex,
525                     tiley,
526                     levelx,
527                     levely,
528                     part->num_tile_levels_x);
529             }
530             if (levely >= part->num_tile_levels_y)
531             {
532                 return ctxt->print_error (
533                     ctxt,
534                     EXR_ERR_INVALID_ARGUMENT,
535                     "Request for tile (%d, %d) level %d, %d, but y level past available levels (%d)",
536                     tilex,
537                     tiley,
538                     levelx,
539                     levely,
540                     part->num_tile_levels_y);
541             }
542 
543             numx = part->tile_level_tile_count_x[levelx];
544             numy = part->tile_level_tile_count_y[levely];
545 
546             if (tilex >= numx || tiley >= numy)
547             {
548                 return ctxt->print_error (
549                     ctxt,
550                     EXR_ERR_INVALID_ARGUMENT,
551                     "Request for tile (%d, %d) at rip level %d, %d level only has %d x %d tiles",
552                     tilex,
553                     tiley,
554                     levelx,
555                     levely,
556                     numx,
557                     numy);
558             }
559 
560             for (int ly = 0; ly < levely; ++ly)
561             {
562                 for (int lx = 0; lx < levelx; ++lx)
563                 {
564                     chunkoff +=
565                         ((int64_t) part->tile_level_tile_count_x[lx] *
566                          (int64_t) part->tile_level_tile_count_y[ly]);
567                 }
568             }
569             for (int lx = 0; lx < levelx; ++lx)
570             {
571                 chunkoff +=
572                     ((int64_t) part->tile_level_tile_count_x[lx] *
573                      (int64_t) numy);
574             }
575             chunkoff += tiley * numx + tilex;
576             break;
577         case EXR_TILE_LAST_TYPE:
578         default:
579             return ctxt->print_error (
580                 ctxt, EXR_ERR_UNKNOWN, "Invalid tile description");
581     }
582 
583     if (chunkoff >= part->chunk_count)
584     {
585         return ctxt->print_error (
586             ctxt,
587             EXR_ERR_UNKNOWN,
588             "Invalid tile chunk offset %" PRId64 " (%d avail)",
589             chunkoff,
590             part->chunk_count);
591     }
592 
593     *chunkoffout = (int32_t) chunkoff;
594     return EXR_ERR_SUCCESS;
595 }
596 
597 /**************************************/
598 
599 exr_result_t
exr_read_tile_chunk_info(exr_const_context_t ctxt,int part_index,int tilex,int tiley,int levelx,int levely,exr_chunk_info_t * cinfo)600 exr_read_tile_chunk_info (
601     exr_const_context_t ctxt,
602     int                 part_index,
603     int                 tilex,
604     int                 tiley,
605     int                 levelx,
606     int                 levely,
607     exr_chunk_info_t*   cinfo)
608 {
609     exr_result_t               rv;
610     int32_t                    data[6];
611     int32_t*                   tdata;
612     int32_t                    cidx, ntoread;
613     uint64_t                   chunkmin, dataoff;
614     int64_t                    nread, fsize, tend, dend;
615     const exr_attr_chlist_t*   chanlist;
616     const exr_attr_tiledesc_t* tiledesc;
617     int                        tilew, tileh;
618     uint64_t                   texels, unpacksize = 0;
619     uint64_t*                  ctable;
620     EXR_PROMOTE_READ_CONST_CONTEXT_AND_PART_OR_ERROR (ctxt, part_index);
621 
622     if (!cinfo) return pctxt->standard_error (pctxt, EXR_ERR_INVALID_ARGUMENT);
623 
624     if (tilex < 0 || tiley < 0 || levelx < 0 || levely < 0)
625         return pctxt->standard_error (pctxt, EXR_ERR_INVALID_ARGUMENT);
626     if (part->storage_mode == EXR_STORAGE_SCANLINE ||
627         part->storage_mode == EXR_STORAGE_DEEP_SCANLINE)
628     {
629         return pctxt->standard_error (pctxt, EXR_ERR_TILE_SCAN_MIXEDAPI);
630     }
631 
632     if (!part->tiles || part->num_tile_levels_x <= 0 ||
633         part->num_tile_levels_y <= 0 || !part->tile_level_tile_count_x ||
634         !part->tile_level_tile_count_y)
635     {
636         return pctxt->print_error (
637             pctxt, EXR_ERR_MISSING_REQ_ATTR, "Tile data missing or corrupt");
638     }
639 
640     tiledesc = part->tiles->tiledesc;
641 
642     tilew = (int) (tiledesc->x_size);
643     dend  = ((int64_t) part->tile_level_tile_size_x[levelx]);
644     tend  = ((int64_t) tilew) * ((int64_t) (tilex + 1));
645     if (tend > dend)
646     {
647         tend -= dend;
648         if (tend < tilew) tilew = tilew - ((int) tend);
649     }
650 
651     tileh = (int) (tiledesc->y_size);
652     dend  = ((int64_t) part->tile_level_tile_size_y[levely]);
653     tend  = ((int64_t) tileh) * ((int64_t) (tiley + 1));
654     if (tend > dend)
655     {
656         tend -= dend;
657         if (tend < tileh) tileh = tileh - ((int) tend);
658     }
659 
660     cidx = 0;
661     rv   = compute_tile_chunk_off (
662         pctxt, part, tilex, tiley, levelx, levely, &cidx);
663     if (rv != EXR_ERR_SUCCESS) return rv;
664 
665     cinfo->idx         = cidx;
666     cinfo->type        = (uint8_t) part->storage_mode;
667     cinfo->compression = (uint8_t) part->comp_type;
668     cinfo->start_x     = tilex;
669     cinfo->start_y     = tiley;
670     cinfo->height      = tileh;
671     cinfo->width       = tilew;
672     if (levelx > 255 || levely > 255)
673         return pctxt->print_error (
674             pctxt,
675             EXR_ERR_ATTR_SIZE_MISMATCH,
676             "Unable to represent tile level %d, %d in chunk structure",
677             levelx,
678             levely);
679 
680     cinfo->level_x = (uint8_t) levelx;
681     cinfo->level_y = (uint8_t) levely;
682 
683     chanlist = part->channels->chlist;
684     texels   = (uint64_t) tilew * (uint64_t) tileh;
685     for (int c = 0; c < chanlist->num_channels; ++c)
686     {
687         const exr_attr_chlist_entry_t* curc = (chanlist->entries + c);
688         unpacksize +=
689             texels * (uint64_t) ((curc->pixel_type == EXR_PIXEL_HALF) ? 2 : 4);
690     }
691 
692     rv = extract_chunk_table (pctxt, part, &ctable, &chunkmin);
693     if (rv != EXR_ERR_SUCCESS) return rv;
694 
695     if (part->storage_mode == EXR_STORAGE_DEEP_TILED)
696     {
697         if (pctxt->is_multipart)
698             ntoread = 5;
699         else
700             ntoread = 4;
701     }
702     else if (pctxt->is_multipart)
703         ntoread = 6;
704     else
705         ntoread = 5;
706 
707     fsize = pctxt->file_size;
708 
709     dataoff = ctable[cidx];
710     if (dataoff < chunkmin || (fsize > 0 && dataoff > (uint64_t) fsize))
711     {
712         return pctxt->print_error (
713             pctxt,
714             EXR_ERR_BAD_CHUNK_LEADER,
715             "Corrupt chunk offset table: tile (%d, %d), level (%d, %d), chunk index %d recorded at file offset %" PRIu64,
716             tilex,
717             tiley,
718             levelx,
719             levely,
720             cidx,
721             dataoff);
722     }
723 
724     rv = pctxt->do_read (
725         pctxt,
726         data,
727         (uint64_t) (ntoread) * sizeof (int32_t),
728         &dataoff,
729         &nread,
730         EXR_MUST_READ_ALL);
731     if (rv != EXR_ERR_SUCCESS)
732     {
733         return pctxt->print_error (
734             pctxt,
735             rv,
736             "Unable to read information block for tile (%d, %d), level (%d, %d): request %" PRIu64
737             " bytes from offset %" PRIu64 ", got %" PRIu64 " bytes",
738             tilex,
739             tiley,
740             levelx,
741             levely,
742             (uint64_t) (ntoread) * sizeof (int32_t),
743             ctable[cidx],
744             (uint64_t) nread);
745     }
746     priv_to_native32 (data, ntoread);
747 
748     tdata = data;
749     if (pctxt->is_multipart)
750     {
751         if (part_index != data[0])
752         {
753             return pctxt->print_error (
754                 pctxt,
755                 EXR_ERR_BAD_CHUNK_LEADER,
756                 "Corrupt tile (%d, %d), level (%d, %d) (chunk %d): bad part number (%d, expect %d)",
757                 tilex,
758                 tiley,
759                 levelx,
760                 levely,
761                 cidx,
762                 data[0],
763                 part_index);
764         }
765         ++tdata;
766     }
767     if (tdata[0] != tilex)
768     {
769         return pctxt->print_error (
770             pctxt,
771             EXR_ERR_BAD_CHUNK_LEADER,
772             "Corrupt tile (%d, %d), level (%d, %d) (chunk %d): bad tile x coordinate (%d, expect %d)",
773             tilex,
774             tiley,
775             levelx,
776             levely,
777             cidx,
778             tdata[0],
779             tilex);
780     }
781     if (tdata[1] != tiley)
782     {
783         return pctxt->print_error (
784             pctxt,
785             EXR_ERR_BAD_CHUNK_LEADER,
786             "Corrupt tile (%d, %d), level (%d, %d) (chunk %d): bad tile Y coordinate (%d, expect %d)",
787             tilex,
788             tiley,
789             levelx,
790             levely,
791             cidx,
792             tdata[1],
793             tiley);
794     }
795     if (tdata[2] != levelx)
796     {
797         return pctxt->print_error (
798             pctxt,
799             EXR_ERR_BAD_CHUNK_LEADER,
800             "Corrupt tile (%d, %d), level (%d, %d) (chunk %d): bad tile mip/rip level X (%d, expect %d)",
801             tilex,
802             tiley,
803             levelx,
804             levely,
805             cidx,
806             tdata[2],
807             levelx);
808     }
809     if (tdata[3] != levely)
810     {
811         return pctxt->print_error (
812             pctxt,
813             EXR_ERR_BAD_CHUNK_LEADER,
814             "Corrupt tile (%d, %d), level (%d, %d) (chunk %d): bad tile mip/rip level Y (%d, expect %d)",
815             tilex,
816             tiley,
817             levelx,
818             levely,
819             cidx,
820             tdata[3],
821             levely);
822     }
823 
824     if (part->storage_mode == EXR_STORAGE_DEEP_TILED)
825     {
826         int64_t ddata[3];
827         rv = pctxt->do_read (
828             pctxt,
829             ddata,
830             3 * sizeof (int64_t),
831             &dataoff,
832             NULL,
833             EXR_MUST_READ_ALL);
834         if (rv != EXR_ERR_SUCCESS) { return rv; }
835         priv_to_native64 (ddata, 3);
836 
837         if (ddata[0] < 0 || (ddata[0] == 0 && (ddata[1] != 0 || ddata[2] != 0)))
838         {
839             return pctxt->print_error (
840                 pctxt,
841                 EXR_ERR_BAD_CHUNK_LEADER,
842                 "Corrupt deep tile (%d, %d), level (%d, %d) (chunk %d): invalid sample table size %" PRId64,
843                 tilex,
844                 tiley,
845                 levelx,
846                 levely,
847                 cidx,
848                 ddata[0]);
849         }
850 
851         /* not all compressors support 64-bit */
852         if (ddata[1] < 0 || ddata[1] > (int64_t) INT32_MAX ||
853             (ddata[1] == 0 && ddata[2] != 0))
854         {
855             return pctxt->print_error (
856                 pctxt,
857                 EXR_ERR_BAD_CHUNK_LEADER,
858                 "Corrupt deep tile (%d, %d), level (%d, %d) (chunk %d): invalid packed data size %" PRId64,
859                 tilex,
860                 tiley,
861                 levelx,
862                 levely,
863                 cidx,
864                 ddata[1]);
865         }
866 
867         if (ddata[2] < 0 || ddata[2] > (int64_t) INT32_MAX ||
868             (ddata[2] == 0 && ddata[1] != 0))
869         {
870             return pctxt->print_error (
871                 pctxt,
872                 EXR_ERR_BAD_CHUNK_LEADER,
873                 "Corrupt deep tile (%d, %d), level (%d, %d) (chunk %d): invalid unpacked size %" PRId64,
874                 tilex,
875                 tiley,
876                 levelx,
877                 levely,
878                 cidx,
879                 ddata[1]);
880         }
881         cinfo->sample_count_data_offset = dataoff;
882         cinfo->sample_count_table_size  = (uint64_t) ddata[0];
883         cinfo->packed_size              = (uint64_t) ddata[1];
884         cinfo->unpacked_size            = (uint64_t) ddata[2];
885         cinfo->data_offset              = dataoff + (uint64_t) ddata[0];
886 
887         if (fsize > 0 &&
888             ((cinfo->sample_count_data_offset +
889               cinfo->sample_count_table_size) > ((uint64_t) fsize) ||
890              (cinfo->data_offset + cinfo->packed_size) > ((uint64_t) fsize)))
891         {
892             return pctxt->print_error (
893                 pctxt,
894                 EXR_ERR_BAD_CHUNK_LEADER,
895                 "Corrupt deep tile (%d, %d), level (%d, %d) (chunk %d): access past end of the file: sample table size %" PRId64
896                 " + data size %" PRId64 " larger than file %" PRId64,
897                 tilex,
898                 tiley,
899                 levelx,
900                 levely,
901                 cidx,
902                 ddata[0],
903                 ddata[1],
904                 fsize);
905         }
906     }
907     else
908     {
909         if (tdata[4] < 0 || ((uint64_t) tdata[4]) > unpacksize ||
910             (tdata[4] == 0 && unpacksize != 0))
911         {
912             return pctxt->print_error (
913                 pctxt,
914                 EXR_ERR_BAD_CHUNK_LEADER,
915                 "Corrupt tile (%d, %d), level (%d, %d) (chunk %d): invalid packed size %d vs unpacked size %" PRIu64,
916                 tilex,
917                 tiley,
918                 levelx,
919                 levely,
920                 cidx,
921                 (int) tdata[4],
922                 unpacksize);
923         }
924         else if (fsize > 0)
925         {
926             uint64_t finpos = dataoff + (uint64_t) tdata[4];
927             if (finpos > fsize)
928             {
929                 return pctxt->print_error (
930                     pctxt,
931                     EXR_ERR_BAD_CHUNK_LEADER,
932                     "Corrupt tile (%d, %d), level (%d, %d) (chunk %d): access past end of file: packed size (%d) at offset %" PRIu64
933                     " vs size of file %" PRId64,
934                     tilex,
935                     tiley,
936                     levelx,
937                     levely,
938                     cidx,
939                     (int) tdata[4],
940                     dataoff,
941                     fsize);
942             }
943         }
944 
945         cinfo->packed_size              = (uint64_t) tdata[4];
946         cinfo->unpacked_size            = unpacksize;
947         cinfo->data_offset              = dataoff;
948         cinfo->sample_count_data_offset = 0;
949         cinfo->sample_count_table_size  = 0;
950     }
951 
952     if (cinfo->packed_size == 0 && cinfo->unpacked_size > 0)
953         return pctxt->report_error (
954             pctxt,
955             EXR_ERR_INVALID_ARGUMENT,
956             "Invalid packed size of 0");
957 
958     return EXR_ERR_SUCCESS;
959 }
960 
961 exr_result_t
exr_read_chunk(exr_const_context_t ctxt,int part_index,const exr_chunk_info_t * cinfo,void * packed_data)962 exr_read_chunk (
963     exr_const_context_t     ctxt,
964     int                     part_index,
965     const exr_chunk_info_t* cinfo,
966     void*                   packed_data)
967 {
968     exr_result_t                 rv;
969     uint64_t                     dataoffset, toread;
970     int64_t                      nread;
971     enum _INTERNAL_EXR_READ_MODE rmode = EXR_MUST_READ_ALL;
972     EXR_PROMOTE_READ_CONST_CONTEXT_AND_PART_OR_ERROR (ctxt, part_index);
973 
974     if (!cinfo) return pctxt->standard_error (pctxt, EXR_ERR_INVALID_ARGUMENT);
975     if (cinfo->packed_size > 0 && !packed_data)
976         return pctxt->standard_error (pctxt, EXR_ERR_INVALID_ARGUMENT);
977 
978     if (cinfo->idx < 0 || cinfo->idx >= part->chunk_count)
979         return pctxt->print_error (
980             pctxt,
981             EXR_ERR_INVALID_ARGUMENT,
982             "invalid chunk index (%d) vs part chunk count %d",
983             cinfo->idx,
984             part->chunk_count);
985     if (cinfo->type != (uint8_t) part->storage_mode)
986         return pctxt->report_error (
987             pctxt,
988             EXR_ERR_INVALID_ARGUMENT,
989             "mis-matched storage type for chunk block info");
990     if (cinfo->compression != (uint8_t) part->comp_type)
991         return pctxt->report_error (
992             pctxt,
993             EXR_ERR_INVALID_ARGUMENT,
994             "mis-matched compression type for chunk block info");
995 
996     dataoffset = cinfo->data_offset;
997     if (pctxt->file_size > 0 && dataoffset > (uint64_t) pctxt->file_size)
998         return pctxt->print_error (
999             pctxt,
1000             EXR_ERR_INVALID_ARGUMENT,
1001             "chunk block info data offset (%" PRIu64
1002             ") past end of file (%" PRId64 ")",
1003             dataoffset,
1004             pctxt->file_size);
1005 
1006     /* allow a short read if uncompressed */
1007     if (part->comp_type == EXR_COMPRESSION_NONE) rmode = EXR_ALLOW_SHORT_READ;
1008 
1009     toread = cinfo->packed_size;
1010     if (toread > 0)
1011     {
1012         nread = 0;
1013         rv    = pctxt->do_read (
1014             pctxt, packed_data, toread, &dataoffset, &nread, rmode);
1015 
1016         if (rmode == EXR_ALLOW_SHORT_READ && nread < (int64_t) toread)
1017             memset (
1018                 ((uint8_t*) packed_data) + nread,
1019                 0,
1020                 toread - (uint64_t) (nread));
1021     }
1022     else
1023         rv = EXR_ERR_SUCCESS;
1024 
1025     return rv;
1026 }
1027 
1028 /**************************************/
1029 
1030 exr_result_t
exr_read_deep_chunk(exr_const_context_t ctxt,int part_index,const exr_chunk_info_t * cinfo,void * packed_data,void * sample_data)1031 exr_read_deep_chunk (
1032     exr_const_context_t     ctxt,
1033     int                     part_index,
1034     const exr_chunk_info_t* cinfo,
1035     void*                   packed_data,
1036     void*                   sample_data)
1037 {
1038     exr_result_t                 rv;
1039     uint64_t                     dataoffset, toread;
1040     int64_t                      nread;
1041     enum _INTERNAL_EXR_READ_MODE rmode = EXR_MUST_READ_ALL;
1042     EXR_PROMOTE_READ_CONST_CONTEXT_AND_PART_OR_ERROR (ctxt, part_index);
1043 
1044     if (!cinfo) return pctxt->standard_error (pctxt, EXR_ERR_INVALID_ARGUMENT);
1045 
1046     if (cinfo->idx < 0 || cinfo->idx >= part->chunk_count)
1047         return pctxt->print_error (
1048             pctxt,
1049             EXR_ERR_INVALID_ARGUMENT,
1050             "invalid chunk index (%d) vs part chunk count %d",
1051             cinfo->idx,
1052             part->chunk_count);
1053     if (cinfo->type != (uint8_t) part->storage_mode)
1054         return pctxt->report_error (
1055             pctxt,
1056             EXR_ERR_INVALID_ARGUMENT,
1057             "mis-matched storage type for chunk block info");
1058     if (cinfo->compression != (uint8_t) part->comp_type)
1059         return pctxt->report_error (
1060             pctxt,
1061             EXR_ERR_INVALID_ARGUMENT,
1062             "mis-matched compression type for chunk block info");
1063 
1064     if (pctxt->file_size > 0 &&
1065         cinfo->sample_count_data_offset > (uint64_t) pctxt->file_size)
1066         return pctxt->print_error (
1067             pctxt,
1068             EXR_ERR_INVALID_ARGUMENT,
1069             "chunk block info sample count offset (%" PRIu64
1070             ") past end of file (%" PRId64 ")",
1071             cinfo->sample_count_data_offset,
1072             pctxt->file_size);
1073 
1074     if (pctxt->file_size > 0 &&
1075         cinfo->data_offset > (uint64_t) pctxt->file_size)
1076         return pctxt->print_error (
1077             pctxt,
1078             EXR_ERR_INVALID_ARGUMENT,
1079             "chunk block info data offset (%" PRIu64
1080             ") past end of file (%" PRId64 ")",
1081             cinfo->data_offset,
1082             pctxt->file_size);
1083 
1084     rv = EXR_ERR_SUCCESS;
1085     if (sample_data && cinfo->sample_count_table_size > 0)
1086     {
1087         dataoffset = cinfo->sample_count_data_offset;
1088         toread     = cinfo->sample_count_table_size;
1089         nread      = 0;
1090         rv         = pctxt->do_read (
1091             pctxt, sample_data, toread, &dataoffset, &nread, rmode);
1092     }
1093 
1094     if (rv != EXR_ERR_SUCCESS) return rv;
1095 
1096     if (packed_data && cinfo->packed_size > 0)
1097     {
1098         dataoffset = cinfo->data_offset;
1099         toread     = cinfo->packed_size;
1100         nread      = 0;
1101         rv         = pctxt->do_read (
1102             pctxt, packed_data, toread, &dataoffset, &nread, rmode);
1103     }
1104 
1105     return rv;
1106 }
1107 
1108 /**************************************/
1109 
1110 /* pull most of the logic to here to avoid having to unlock at every
1111  * error exit point and re-use mostly shared logic */
1112 static exr_result_t
write_scan_chunk(struct _internal_exr_context * pctxt,int part_index,struct _internal_exr_part * part,int y,const void * packed_data,uint64_t packed_size,uint64_t unpacked_size,const void * sample_data,uint64_t sample_data_size)1113 write_scan_chunk (
1114     struct _internal_exr_context* pctxt,
1115     int                           part_index,
1116     struct _internal_exr_part*    part,
1117     int                           y,
1118     const void*                   packed_data,
1119     uint64_t                      packed_size,
1120     uint64_t                      unpacked_size,
1121     const void*                   sample_data,
1122     uint64_t                      sample_data_size)
1123 {
1124     exr_result_t rv;
1125     int32_t      data[3];
1126     int32_t      psize;
1127     int          cidx, lpc, miny, wrcnt;
1128     uint64_t*    ctable;
1129 
1130     if (pctxt->mode != EXR_CONTEXT_WRITING_DATA)
1131     {
1132         if (pctxt->mode == EXR_CONTEXT_WRITE)
1133             return pctxt->standard_error (pctxt, EXR_ERR_HEADER_NOT_WRITTEN);
1134         return pctxt->standard_error (pctxt, EXR_ERR_NOT_OPEN_WRITE);
1135     }
1136 
1137     if (part->storage_mode == EXR_STORAGE_TILED ||
1138         part->storage_mode == EXR_STORAGE_DEEP_TILED)
1139     {
1140         return pctxt->standard_error (pctxt, EXR_ERR_SCAN_TILE_MIXEDAPI);
1141     }
1142 
1143     if (pctxt->cur_output_part != part_index)
1144         return pctxt->standard_error (pctxt, EXR_ERR_INCORRECT_PART);
1145 
1146     if (packed_size > 0 && !packed_data)
1147         return pctxt->print_error (
1148             pctxt,
1149             EXR_ERR_INVALID_ARGUMENT,
1150             "Invalid packed data argument size %" PRIu64 " pointer %p",
1151             (uint64_t) packed_size,
1152             packed_data);
1153 
1154     if (part->storage_mode != EXR_STORAGE_DEEP_SCANLINE &&
1155         packed_size > (uint64_t) INT32_MAX)
1156         return pctxt->print_error (
1157             pctxt,
1158             EXR_ERR_INVALID_ARGUMENT,
1159             "Packed data size %" PRIu64 " too large (max %" PRIu64 ")",
1160             (uint64_t) packed_size,
1161             (uint64_t) INT32_MAX);
1162     psize = (int32_t) packed_size;
1163 
1164     if (part->storage_mode == EXR_STORAGE_DEEP_SCANLINE &&
1165         (!sample_data || sample_data_size == 0))
1166         return pctxt->print_error (
1167             pctxt,
1168             EXR_ERR_INVALID_ARGUMENT,
1169             "Invalid sample count data argument size %" PRIu64 " pointer %p",
1170             (uint64_t) sample_data_size,
1171             sample_data);
1172 
1173     if (y < part->data_window.min.y || y > part->data_window.max.y)
1174     {
1175         return pctxt->print_error (
1176             pctxt,
1177             EXR_ERR_INVALID_ARGUMENT,
1178             "Invalid attempt to write scanlines starting at %d outside range of data window (%d - %d)",
1179             y,
1180             part->data_window.min.y,
1181             part->data_window.max.y);
1182     }
1183 
1184     lpc  = part->lines_per_chunk;
1185     cidx = (y - part->data_window.min.y);
1186     if (lpc > 1) cidx /= lpc;
1187 
1188     //if (part->lineorder == EXR_LINEORDER_DECREASING_Y)
1189     //    cidx = part->chunk_count - (cidx + 1);
1190 
1191     miny = cidx * lpc + part->data_window.min.y;
1192 
1193     if (y != miny)
1194     {
1195         return pctxt->print_error (
1196             pctxt,
1197             EXR_ERR_INVALID_ARGUMENT,
1198             "Attempt to write scanline %d which does not align with y dims (%d) for chunk index (%d)",
1199             y,
1200             miny,
1201             cidx);
1202     }
1203 
1204     if (cidx < 0 || cidx >= part->chunk_count)
1205     {
1206         return pctxt->print_error (
1207             pctxt,
1208             EXR_ERR_INVALID_ARGUMENT,
1209             "Chunk index for scanline %d in chunk %d outside chunk count %d",
1210             y,
1211             cidx,
1212             part->chunk_count);
1213     }
1214 
1215     if (part->lineorder != EXR_LINEORDER_RANDOM_Y &&
1216         pctxt->last_output_chunk != (cidx - 1))
1217     {
1218         return pctxt->standard_error (pctxt, EXR_ERR_INCORRECT_CHUNK);
1219     }
1220 
1221     if (pctxt->is_multipart)
1222     {
1223         data[0] = part_index;
1224         data[1] = miny;
1225         if (part->storage_mode != EXR_STORAGE_DEEP_SCANLINE)
1226         {
1227             data[2] = psize;
1228             wrcnt   = 3;
1229         }
1230         else
1231             wrcnt = 2;
1232     }
1233     else
1234     {
1235         data[0] = miny;
1236         if (part->storage_mode != EXR_STORAGE_DEEP_SCANLINE)
1237         {
1238             data[1] = psize;
1239             wrcnt   = 2;
1240         }
1241         else
1242             wrcnt = 1;
1243     }
1244     priv_from_native32 (data, wrcnt);
1245 
1246     rv = alloc_chunk_table (pctxt, part, &ctable);
1247     if (rv != EXR_ERR_SUCCESS) return rv;
1248 
1249     ctable[cidx] = pctxt->output_file_offset;
1250     rv           = pctxt->do_write (
1251         pctxt,
1252         data,
1253         (uint64_t) (wrcnt) * sizeof (int32_t),
1254         &(pctxt->output_file_offset));
1255     if (rv == EXR_ERR_SUCCESS &&
1256         part->storage_mode == EXR_STORAGE_DEEP_SCANLINE)
1257     {
1258         int64_t ddata[3];
1259         ddata[0] = (int64_t) sample_data_size;
1260         ddata[1] = (int64_t) packed_size;
1261         ddata[2] = (int64_t) unpacked_size;
1262         rv       = pctxt->do_write (
1263             pctxt, ddata, 3 * sizeof (uint64_t), &(pctxt->output_file_offset));
1264 
1265         if (rv == EXR_ERR_SUCCESS)
1266             rv = pctxt->do_write (
1267                 pctxt,
1268                 sample_data,
1269                 sample_data_size,
1270                 &(pctxt->output_file_offset));
1271     }
1272     if (rv == EXR_ERR_SUCCESS && packed_size > 0)
1273         rv = pctxt->do_write (
1274             pctxt, packed_data, packed_size, &(pctxt->output_file_offset));
1275 
1276     if (rv == EXR_ERR_SUCCESS)
1277     {
1278         ++(pctxt->output_chunk_count);
1279         if (pctxt->output_chunk_count == part->chunk_count)
1280         {
1281             uint64_t chunkoff = part->chunk_table_offset;
1282 
1283             ++(pctxt->cur_output_part);
1284             if (pctxt->cur_output_part == pctxt->num_parts)
1285                 pctxt->mode = EXR_CONTEXT_WRITE_FINISHED;
1286             pctxt->last_output_chunk  = -1;
1287             pctxt->output_chunk_count = 0;
1288 
1289             priv_from_native64 (ctable, part->chunk_count);
1290             rv = pctxt->do_write (
1291                 pctxt,
1292                 ctable,
1293                 sizeof (uint64_t) * (uint64_t) (part->chunk_count),
1294                 &chunkoff);
1295             /* just in case we look at it again? */
1296             priv_to_native64 (ctable, part->chunk_count);
1297         }
1298         else
1299         {
1300             pctxt->last_output_chunk = cidx;
1301         }
1302     }
1303 
1304     return rv;
1305 }
1306 
1307 /**************************************/
1308 
1309 exr_result_t
exr_write_scanline_chunk_info(exr_context_t ctxt,int part_index,int y,exr_chunk_info_t * cinfo)1310 exr_write_scanline_chunk_info (
1311     exr_context_t ctxt, int part_index, int y, exr_chunk_info_t* cinfo)
1312 {
1313     exr_attr_box2i_t dw;
1314     int              lpc, miny, cidx;
1315     exr_chunk_info_t nil = { 0 };
1316 
1317     EXR_PROMOTE_LOCKED_CONTEXT_AND_PART_OR_ERROR (ctxt, part_index);
1318 
1319     if (!cinfo)
1320         return EXR_UNLOCK_AND_RETURN_PCTXT (
1321             pctxt->standard_error (pctxt, EXR_ERR_INVALID_ARGUMENT));
1322 
1323     if (part->storage_mode == EXR_STORAGE_TILED ||
1324         part->storage_mode == EXR_STORAGE_DEEP_TILED)
1325     {
1326         return EXR_UNLOCK_AND_RETURN_PCTXT (
1327             pctxt->standard_error (pctxt, EXR_ERR_SCAN_TILE_MIXEDAPI));
1328     }
1329 
1330     if (pctxt->mode != EXR_CONTEXT_WRITING_DATA)
1331     {
1332         if (pctxt->mode == EXR_CONTEXT_WRITE)
1333             return EXR_UNLOCK_AND_RETURN_PCTXT (
1334                 pctxt->standard_error (pctxt, EXR_ERR_HEADER_NOT_WRITTEN));
1335         return EXR_UNLOCK_AND_RETURN_PCTXT (
1336             pctxt->standard_error (pctxt, EXR_ERR_NOT_OPEN_WRITE));
1337     }
1338 
1339     dw = part->data_window;
1340     if (y < dw.min.y || y > dw.max.y)
1341     {
1342         return EXR_UNLOCK_AND_RETURN_PCTXT (pctxt->print_error (
1343             pctxt,
1344             EXR_ERR_INVALID_ARGUMENT,
1345             "Invalid request for scanline %d outside range of data window (%d - %d)",
1346             y,
1347             dw.min.y,
1348             dw.max.y));
1349     }
1350 
1351     lpc  = part->lines_per_chunk;
1352     cidx = (y - dw.min.y);
1353     if (lpc > 1) cidx /= lpc;
1354 
1355     //if (part->lineorder == EXR_LINEORDER_DECREASING_Y)
1356     //    cidx = part->chunk_count - (cidx + 1);
1357     miny = cidx * lpc + dw.min.y;
1358 
1359     if (cidx < 0 || cidx >= part->chunk_count)
1360     {
1361         return EXR_UNLOCK_AND_RETURN_PCTXT (pctxt->print_error (
1362             pctxt,
1363             EXR_ERR_INVALID_ARGUMENT,
1364             "Invalid request for scanline %d in chunk %d outside chunk count %d",
1365             y,
1366             cidx,
1367             part->chunk_count));
1368     }
1369 
1370     *cinfo             = nil;
1371     cinfo->idx         = cidx;
1372     cinfo->type        = (uint8_t) part->storage_mode;
1373     cinfo->compression = (uint8_t) part->comp_type;
1374     cinfo->start_x     = dw.min.x;
1375     cinfo->start_y     = miny;
1376     cinfo->width       = dw.max.x - dw.min.x + 1;
1377     cinfo->height      = lpc;
1378     if (miny < dw.min.y)
1379     {
1380         cinfo->start_y = dw.min.y;
1381         cinfo->height -= (dw.min.y - miny);
1382     }
1383     else if ((miny + lpc) > dw.max.y)
1384     {
1385         cinfo->height = (dw.max.y - miny + 1);
1386     }
1387     cinfo->level_x = 0;
1388     cinfo->level_y = 0;
1389 
1390     cinfo->sample_count_data_offset = 0;
1391     cinfo->sample_count_table_size  = 0;
1392     cinfo->data_offset              = 0;
1393     cinfo->packed_size              = 0;
1394     cinfo->unpacked_size =
1395         compute_chunk_unpack_size (y, cinfo->width, cinfo->height, lpc, part);
1396 
1397     return EXR_UNLOCK_AND_RETURN_PCTXT (EXR_ERR_SUCCESS);
1398 }
1399 
1400 /**************************************/
1401 
1402 exr_result_t
exr_write_tile_chunk_info(exr_context_t ctxt,int part_index,int tilex,int tiley,int levelx,int levely,exr_chunk_info_t * cinfo)1403 exr_write_tile_chunk_info (
1404     exr_context_t     ctxt,
1405     int               part_index,
1406     int               tilex,
1407     int               tiley,
1408     int               levelx,
1409     int               levely,
1410     exr_chunk_info_t* cinfo)
1411 {
1412     exr_result_t               rv;
1413     int                        cidx;
1414     const exr_attr_chlist_t*   chanlist;
1415     const exr_attr_tiledesc_t* tiledesc;
1416     int                        tilew, tileh;
1417     uint64_t                   unpacksize = 0;
1418     exr_chunk_info_t           nil        = { 0 };
1419 
1420     EXR_PROMOTE_LOCKED_CONTEXT_AND_PART_OR_ERROR (ctxt, part_index);
1421 
1422     if (!cinfo)
1423         return EXR_UNLOCK_AND_RETURN_PCTXT (
1424             pctxt->standard_error (pctxt, EXR_ERR_INVALID_ARGUMENT));
1425 
1426     if (tilex < 0 || tiley < 0 || levelx < 0 || levely < 0)
1427         return EXR_UNLOCK_AND_RETURN_PCTXT (
1428             pctxt->standard_error (pctxt, EXR_ERR_INVALID_ARGUMENT));
1429     if (part->storage_mode == EXR_STORAGE_SCANLINE ||
1430         part->storage_mode == EXR_STORAGE_DEEP_SCANLINE)
1431     {
1432         return EXR_UNLOCK_AND_RETURN_PCTXT (
1433             pctxt->standard_error (pctxt, EXR_ERR_TILE_SCAN_MIXEDAPI));
1434     }
1435 
1436     if (!part->tiles || part->num_tile_levels_x <= 0 ||
1437         part->num_tile_levels_y <= 0 || !part->tile_level_tile_count_x ||
1438         !part->tile_level_tile_count_y)
1439     {
1440         return EXR_UNLOCK_WRITE_AND_RETURN_PCTXT (pctxt->report_error (
1441             pctxt, EXR_ERR_MISSING_REQ_ATTR, "Tile data missing or corrupt"));
1442     }
1443 
1444     if (pctxt->mode != EXR_CONTEXT_WRITING_DATA)
1445     {
1446         if (pctxt->mode == EXR_CONTEXT_WRITE)
1447             return EXR_UNLOCK_AND_RETURN_PCTXT (
1448                 pctxt->standard_error (pctxt, EXR_ERR_HEADER_NOT_WRITTEN));
1449         return EXR_UNLOCK_AND_RETURN_PCTXT (
1450             pctxt->standard_error (pctxt, EXR_ERR_NOT_OPEN_WRITE));
1451     }
1452 
1453     tiledesc = part->tiles->tiledesc;
1454     tilew    = part->tile_level_tile_size_x[levelx];
1455     if (tiledesc->x_size < (uint32_t) tilew) tilew = (int) tiledesc->x_size;
1456     tileh = part->tile_level_tile_size_y[levely];
1457     if (tiledesc->y_size < (uint32_t) tileh) tileh = (int) tiledesc->y_size;
1458 
1459     if (((int64_t) (tilex) * (int64_t) (tilew) + (int64_t) (tilew) +
1460          (int64_t) (part->data_window.min.x) - 1) >
1461         (int64_t) (part->data_window.max.x))
1462     {
1463         int64_t sz = (int64_t) (part->data_window.max.x) -
1464                      (int64_t) (part->data_window.min.x) + 1;
1465         tilew = (int) (sz - ((int64_t) (tilex) * (int64_t) (tilew)));
1466     }
1467 
1468     if (((int64_t) (tiley) * (int64_t) (tileh) + (int64_t) (tileh) +
1469          (int64_t) (part->data_window.min.y) - 1) >
1470         (int64_t) (part->data_window.max.y))
1471     {
1472         int64_t sz = (int64_t) (part->data_window.max.y) -
1473                      (int64_t) (part->data_window.min.y) + 1;
1474         tileh = (int) (sz - ((int64_t) (tiley) * (int64_t) (tileh)));
1475     }
1476 
1477     cidx = 0;
1478     rv   = compute_tile_chunk_off (
1479         pctxt, part, tilex, tiley, levelx, levely, &cidx);
1480     if (rv != EXR_ERR_SUCCESS) return EXR_UNLOCK_AND_RETURN_PCTXT (rv);
1481 
1482     *cinfo             = nil;
1483     cinfo->idx         = cidx;
1484     cinfo->type        = (uint8_t) part->storage_mode;
1485     cinfo->compression = (uint8_t) part->comp_type;
1486     cinfo->start_x     = tilex;
1487     cinfo->start_y     = tiley;
1488     cinfo->height      = tileh;
1489     cinfo->width       = tilew;
1490     if (levelx > 255 || levely > 255)
1491         return pctxt->print_error (
1492             pctxt,
1493             EXR_ERR_ATTR_SIZE_MISMATCH,
1494             "Unable to represent tile level %d, %d in chunk structure",
1495             levelx,
1496             levely);
1497 
1498     cinfo->level_x = (uint8_t) levelx;
1499     cinfo->level_y = (uint8_t) levely;
1500 
1501     chanlist = part->channels->chlist;
1502     for (int c = 0; c < chanlist->num_channels; ++c)
1503     {
1504         const exr_attr_chlist_entry_t* curc = (chanlist->entries + c);
1505         unpacksize += (uint64_t) (tilew) * (uint64_t) (tileh) *
1506                       (uint64_t) ((curc->pixel_type == EXR_PIXEL_HALF) ? 2 : 4);
1507     }
1508 
1509     cinfo->sample_count_data_offset = 0;
1510     cinfo->sample_count_table_size  = 0;
1511     cinfo->data_offset              = 0;
1512     cinfo->packed_size              = 0;
1513     cinfo->unpacked_size            = unpacksize;
1514 
1515     return EXR_UNLOCK_AND_RETURN_PCTXT (EXR_ERR_SUCCESS);
1516 }
1517 
1518 /**************************************/
1519 
1520 exr_result_t
exr_write_scanline_chunk(exr_context_t ctxt,int part_index,int y,const void * packed_data,uint64_t packed_size)1521 exr_write_scanline_chunk (
1522     exr_context_t ctxt,
1523     int           part_index,
1524     int           y,
1525     const void*   packed_data,
1526     uint64_t      packed_size)
1527 {
1528     exr_result_t rv;
1529     EXR_PROMOTE_LOCKED_CONTEXT_AND_PART_OR_ERROR (ctxt, part_index);
1530 
1531     if (part->storage_mode == EXR_STORAGE_DEEP_SCANLINE)
1532         return EXR_UNLOCK_AND_RETURN_PCTXT (
1533             pctxt->standard_error (pctxt, EXR_ERR_USE_SCAN_DEEP_WRITE));
1534 
1535     rv = write_scan_chunk (
1536         pctxt, part_index, part, y, packed_data, packed_size, 0, NULL, 0);
1537     return EXR_UNLOCK_AND_RETURN_PCTXT (rv);
1538 }
1539 
1540 /**************************************/
1541 
1542 exr_result_t
exr_write_deep_scanline_chunk(exr_context_t ctxt,int part_index,int y,const void * packed_data,uint64_t packed_size,uint64_t unpacked_size,const void * sample_data,uint64_t sample_data_size)1543 exr_write_deep_scanline_chunk (
1544     exr_context_t ctxt,
1545     int           part_index,
1546     int           y,
1547     const void*   packed_data,
1548     uint64_t      packed_size,
1549     uint64_t      unpacked_size,
1550     const void*   sample_data,
1551     uint64_t      sample_data_size)
1552 {
1553     exr_result_t rv;
1554     EXR_PROMOTE_LOCKED_CONTEXT_AND_PART_OR_ERROR (ctxt, part_index);
1555 
1556     if (part->storage_mode == EXR_STORAGE_SCANLINE)
1557         return EXR_UNLOCK_AND_RETURN_PCTXT (
1558             pctxt->standard_error (pctxt, EXR_ERR_USE_SCAN_NONDEEP_WRITE));
1559 
1560     rv = write_scan_chunk (
1561         pctxt,
1562         part_index,
1563         part,
1564         y,
1565         packed_data,
1566         packed_size,
1567         unpacked_size,
1568         sample_data,
1569         sample_data_size);
1570     return EXR_UNLOCK_AND_RETURN_PCTXT (rv);
1571 }
1572 
1573 /**************************************/
1574 
1575 /* pull most of the logic to here to avoid having to unlock at every
1576  * error exit point and re-use mostly shared logic */
1577 static exr_result_t
write_tile_chunk(struct _internal_exr_context * pctxt,int part_index,struct _internal_exr_part * part,int tilex,int tiley,int levelx,int levely,const void * packed_data,uint64_t packed_size,uint64_t unpacked_size,const void * sample_data,uint64_t sample_data_size)1578 write_tile_chunk (
1579     struct _internal_exr_context* pctxt,
1580     int                           part_index,
1581     struct _internal_exr_part*    part,
1582     int                           tilex,
1583     int                           tiley,
1584     int                           levelx,
1585     int                           levely,
1586     const void*                   packed_data,
1587     uint64_t                      packed_size,
1588     uint64_t                      unpacked_size,
1589     const void*                   sample_data,
1590     uint64_t                      sample_data_size)
1591 {
1592     exr_result_t rv;
1593     int32_t      data[6];
1594     int32_t      psize;
1595     int          cidx, wrcnt;
1596     uint64_t*    ctable;
1597 
1598     if (pctxt->mode != EXR_CONTEXT_WRITING_DATA)
1599     {
1600         if (pctxt->mode == EXR_CONTEXT_WRITE)
1601             return pctxt->standard_error (pctxt, EXR_ERR_HEADER_NOT_WRITTEN);
1602         return pctxt->standard_error (pctxt, EXR_ERR_NOT_OPEN_WRITE);
1603     }
1604 
1605     if (part->storage_mode == EXR_STORAGE_SCANLINE ||
1606         part->storage_mode == EXR_STORAGE_DEEP_SCANLINE)
1607     {
1608         return pctxt->standard_error (pctxt, EXR_ERR_TILE_SCAN_MIXEDAPI);
1609     }
1610 
1611     if (pctxt->cur_output_part != part_index)
1612         return pctxt->standard_error (pctxt, EXR_ERR_INCORRECT_PART);
1613 
1614     if (!packed_data || packed_size == 0)
1615         return pctxt->print_error (
1616             pctxt,
1617             EXR_ERR_INVALID_ARGUMENT,
1618             "Invalid packed data argument size %" PRIu64 " pointer %p",
1619             (uint64_t) packed_size,
1620             packed_data);
1621 
1622     if (part->storage_mode != EXR_STORAGE_DEEP_TILED &&
1623         packed_size > (uint64_t) INT32_MAX)
1624         return pctxt->print_error (
1625             pctxt,
1626             EXR_ERR_INVALID_ARGUMENT,
1627             "Packed data size %" PRIu64 " too large (max %" PRIu64 ")",
1628             (uint64_t) packed_size,
1629             (uint64_t) INT32_MAX);
1630     psize = (int32_t) packed_size;
1631 
1632     if (part->storage_mode == EXR_STORAGE_DEEP_TILED &&
1633         (!sample_data || sample_data_size == 0))
1634         return pctxt->print_error (
1635             pctxt,
1636             EXR_ERR_INVALID_ARGUMENT,
1637             "Invalid sample count data argument size %" PRIu64 " pointer %p",
1638             (uint64_t) sample_data_size,
1639             sample_data);
1640 
1641     if (!part->tiles || part->num_tile_levels_x <= 0 ||
1642         part->num_tile_levels_y <= 0 || !part->tile_level_tile_count_x ||
1643         !part->tile_level_tile_count_y)
1644     {
1645         return pctxt->print_error (
1646             pctxt,
1647             EXR_ERR_MISSING_REQ_ATTR,
1648             "Attempting to write tiled part, but tile data missing or corrupt");
1649     }
1650 
1651     cidx = -1;
1652     rv   = compute_tile_chunk_off (
1653         pctxt, part, tilex, tiley, levelx, levely, &cidx);
1654     if (rv != EXR_ERR_SUCCESS) return rv;
1655 
1656     if (cidx < 0 || cidx >= part->chunk_count)
1657     {
1658         return pctxt->print_error (
1659             pctxt,
1660             EXR_ERR_INVALID_ARGUMENT,
1661             "Chunk index for tile (%d, %d) at level (%d, %d) %d outside chunk count %d",
1662             tilex,
1663             tiley,
1664             levelx,
1665             levely,
1666             cidx,
1667             part->chunk_count);
1668     }
1669 
1670     if (part->lineorder != EXR_LINEORDER_RANDOM_Y &&
1671         pctxt->last_output_chunk != (cidx - 1))
1672     {
1673         return pctxt->print_error (
1674             pctxt,
1675             EXR_ERR_INCORRECT_CHUNK,
1676             "Chunk index %d is not the next chunk to be written (last %d)",
1677             cidx,
1678             pctxt->last_output_chunk);
1679     }
1680 
1681     wrcnt = 0;
1682     if (pctxt->is_multipart) { data[wrcnt++] = part_index; }
1683     data[wrcnt++] = tilex;
1684     data[wrcnt++] = tiley;
1685     data[wrcnt++] = levelx;
1686     data[wrcnt++] = levely;
1687     if (part->storage_mode != EXR_STORAGE_DEEP_TILED) data[wrcnt++] = psize;
1688 
1689     priv_from_native32 (data, wrcnt);
1690 
1691     rv = alloc_chunk_table (pctxt, part, &ctable);
1692     if (rv != EXR_ERR_SUCCESS) return rv;
1693 
1694     ctable[cidx] = pctxt->output_file_offset;
1695     rv           = pctxt->do_write (
1696         pctxt,
1697         data,
1698         (uint64_t) (wrcnt) * sizeof (int32_t),
1699         &(pctxt->output_file_offset));
1700     if (rv == EXR_ERR_SUCCESS && part->storage_mode == EXR_STORAGE_DEEP_TILED)
1701     {
1702         int64_t ddata[3];
1703         ddata[0] = (int64_t) sample_data_size;
1704         ddata[1] = (int64_t) packed_size;
1705         ddata[2] = (int64_t) unpacked_size;
1706         rv       = pctxt->do_write (
1707             pctxt, ddata, 3 * sizeof (uint64_t), &(pctxt->output_file_offset));
1708 
1709         if (rv == EXR_ERR_SUCCESS)
1710             rv = pctxt->do_write (
1711                 pctxt,
1712                 sample_data,
1713                 sample_data_size,
1714                 &(pctxt->output_file_offset));
1715     }
1716     if (rv == EXR_ERR_SUCCESS)
1717         rv = pctxt->do_write (
1718             pctxt, packed_data, packed_size, &(pctxt->output_file_offset));
1719 
1720     if (rv == EXR_ERR_SUCCESS)
1721     {
1722         ++(pctxt->output_chunk_count);
1723         if (pctxt->output_chunk_count == part->chunk_count)
1724         {
1725             uint64_t chunkoff = part->chunk_table_offset;
1726 
1727             ++(pctxt->cur_output_part);
1728             if (pctxt->cur_output_part == pctxt->num_parts)
1729                 pctxt->mode = EXR_CONTEXT_WRITE_FINISHED;
1730             pctxt->last_output_chunk  = -1;
1731             pctxt->output_chunk_count = 0;
1732 
1733             priv_from_native64 (ctable, part->chunk_count);
1734             rv = pctxt->do_write (
1735                 pctxt,
1736                 ctable,
1737                 sizeof (uint64_t) * (uint64_t) (part->chunk_count),
1738                 &chunkoff);
1739             /* just in case we look at it again? */
1740             priv_to_native64 (ctable, part->chunk_count);
1741         }
1742         else
1743         {
1744             pctxt->last_output_chunk = cidx;
1745         }
1746     }
1747 
1748     return rv;
1749 }
1750 
1751 /**************************************/
1752 
1753 exr_result_t
exr_write_tile_chunk(exr_context_t ctxt,int part_index,int tilex,int tiley,int levelx,int levely,const void * packed_data,uint64_t packed_size)1754 exr_write_tile_chunk (
1755     exr_context_t ctxt,
1756     int           part_index,
1757     int           tilex,
1758     int           tiley,
1759     int           levelx,
1760     int           levely,
1761     const void*   packed_data,
1762     uint64_t      packed_size)
1763 {
1764     exr_result_t rv;
1765     EXR_PROMOTE_LOCKED_CONTEXT_AND_PART_OR_ERROR (ctxt, part_index);
1766 
1767     if (part->storage_mode == EXR_STORAGE_DEEP_TILED)
1768         return EXR_UNLOCK_AND_RETURN_PCTXT (
1769             pctxt->standard_error (pctxt, EXR_ERR_USE_TILE_DEEP_WRITE));
1770 
1771     rv = write_tile_chunk (
1772         pctxt,
1773         part_index,
1774         part,
1775         tilex,
1776         tiley,
1777         levelx,
1778         levely,
1779         packed_data,
1780         packed_size,
1781         0,
1782         NULL,
1783         0);
1784     return EXR_UNLOCK_AND_RETURN_PCTXT (rv);
1785 }
1786 
1787 /**************************************/
1788 
1789 exr_result_t
exr_write_deep_tile_chunk(exr_context_t ctxt,int part_index,int tilex,int tiley,int levelx,int levely,const void * packed_data,uint64_t packed_size,uint64_t unpacked_size,const void * sample_data,uint64_t sample_data_size)1790 exr_write_deep_tile_chunk (
1791     exr_context_t ctxt,
1792     int           part_index,
1793     int           tilex,
1794     int           tiley,
1795     int           levelx,
1796     int           levely,
1797     const void*   packed_data,
1798     uint64_t      packed_size,
1799     uint64_t      unpacked_size,
1800     const void*   sample_data,
1801     uint64_t      sample_data_size)
1802 {
1803     exr_result_t rv;
1804     EXR_PROMOTE_LOCKED_CONTEXT_AND_PART_OR_ERROR (ctxt, part_index);
1805 
1806     if (part->storage_mode == EXR_STORAGE_TILED)
1807         return EXR_UNLOCK_AND_RETURN_PCTXT (
1808             pctxt->standard_error (pctxt, EXR_ERR_USE_TILE_NONDEEP_WRITE));
1809 
1810     rv = write_tile_chunk (
1811         pctxt,
1812         part_index,
1813         part,
1814         tilex,
1815         tiley,
1816         levelx,
1817         levely,
1818         packed_data,
1819         packed_size,
1820         unpacked_size,
1821         sample_data,
1822         sample_data_size);
1823     return EXR_UNLOCK_AND_RETURN_PCTXT (rv);
1824 }
1825 
1826 /**************************************/
1827 
1828 exr_result_t
internal_validate_next_chunk(exr_encode_pipeline_t * encode,const struct _internal_exr_context * pctxt,const struct _internal_exr_part * part)1829 internal_validate_next_chunk (
1830     exr_encode_pipeline_t*              encode,
1831     const struct _internal_exr_context* pctxt,
1832     const struct _internal_exr_part*    part)
1833 {
1834     exr_result_t rv = EXR_ERR_SUCCESS;
1835     int          cidx, lpc;
1836 
1837     if (pctxt->cur_output_part != encode->part_index)
1838         return pctxt->standard_error (pctxt, EXR_ERR_INCORRECT_PART);
1839 
1840     cidx = -1;
1841 
1842     if (part->storage_mode == EXR_STORAGE_TILED ||
1843         part->storage_mode == EXR_STORAGE_DEEP_TILED)
1844     {
1845         rv = compute_tile_chunk_off (
1846             pctxt,
1847             part,
1848             encode->chunk.start_x,
1849             encode->chunk.start_y,
1850             encode->chunk.level_x,
1851             encode->chunk.level_y,
1852             &cidx);
1853     }
1854     else
1855     {
1856         lpc  = part->lines_per_chunk;
1857         cidx = (encode->chunk.start_y - part->data_window.min.y);
1858         if (lpc > 1) cidx /= lpc;
1859 
1860         //if (part->lineorder == EXR_LINEORDER_DECREASING_Y)
1861         //{
1862         //    cidx = part->chunk_count - (cidx + 1);
1863         //}
1864     }
1865 
1866     if (rv == EXR_ERR_SUCCESS)
1867     {
1868         if (cidx < 0 || cidx >= part->chunk_count)
1869         {
1870             rv = pctxt->print_error (
1871                 pctxt,
1872                 EXR_ERR_INVALID_ARGUMENT,
1873                 "Chunk index for scanline %d in chunk %d outside chunk count %d",
1874                 encode->chunk.start_y,
1875                 cidx,
1876                 part->chunk_count);
1877         }
1878         else if (
1879             part->lineorder != EXR_LINEORDER_RANDOM_Y &&
1880             pctxt->last_output_chunk != (cidx - 1))
1881         {
1882             rv = pctxt->print_error (
1883                 pctxt,
1884                 EXR_ERR_INCORRECT_CHUNK,
1885                 "Attempt to write chunk %d, but last output chunk is %d",
1886                 cidx,
1887                 pctxt->last_output_chunk);
1888         }
1889     }
1890     return rv;
1891 }
1892