1 //
2 // SPDX-License-Identifier: BSD-3-Clause
3 // Copyright (c) Contributors to the OpenEXR Project.
4 //
5
6 //-----------------------------------------------------------------------------
7 //
8 // class TileOffsets
9 //
10 //-----------------------------------------------------------------------------
11
12 #include <ImfTileOffsets.h>
13 #include <ImfXdr.h>
14 #include <ImfIO.h>
15 #include "Iex.h"
16 #include "ImfNamespace.h"
17 #include <algorithm>
18
19 OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER
20
21
TileOffsets(LevelMode mode,int numXLevels,int numYLevels,const int * numXTiles,const int * numYTiles)22 TileOffsets::TileOffsets (LevelMode mode,
23 int numXLevels, int numYLevels,
24 const int *numXTiles, const int *numYTiles)
25 :
26 _mode (mode),
27 _numXLevels (numXLevels),
28 _numYLevels (numYLevels)
29 {
30 switch (_mode)
31 {
32 case ONE_LEVEL:
33 case MIPMAP_LEVELS:
34
35 _offsets.resize (_numXLevels);
36
37 for (unsigned int l = 0; l < _offsets.size(); ++l)
38 {
39 _offsets[l].resize (numYTiles[l]);
40
41 for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
42 {
43 _offsets[l][dy].resize (numXTiles[l]);
44 }
45 }
46 break;
47
48 case RIPMAP_LEVELS:
49
50 _offsets.resize (_numXLevels * _numYLevels);
51
52 for (int ly = 0; ly < _numYLevels; ++ly)
53 {
54 for (int lx = 0; lx < _numXLevels; ++lx)
55 {
56 int l = ly * _numXLevels + lx;
57 _offsets[l].resize (numYTiles[ly]);
58
59 for (size_t dy = 0; dy < _offsets[l].size(); ++dy)
60 {
61 _offsets[l][dy].resize (numXTiles[lx]);
62 }
63 }
64 }
65 break;
66
67 case NUM_LEVELMODES :
68 throw IEX_NAMESPACE::ArgExc("Bad initialisation of TileOffsets object");
69 }
70 }
71
72
73 bool
anyOffsetsAreInvalid() const74 TileOffsets::anyOffsetsAreInvalid () const
75 {
76 for (unsigned int l = 0; l < _offsets.size(); ++l)
77 for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
78 for (unsigned int dx = 0; dx < _offsets[l][dy].size(); ++dx)
79 if (_offsets[l][dy][dx] <= 0)
80 return true;
81
82 return false;
83 }
84
85
86 void
findTiles(OPENEXR_IMF_INTERNAL_NAMESPACE::IStream & is,bool isMultiPartFile,bool isDeep,bool skipOnly)87 TileOffsets::findTiles (OPENEXR_IMF_INTERNAL_NAMESPACE::IStream &is, bool isMultiPartFile, bool isDeep, bool skipOnly)
88 {
89 for (unsigned int l = 0; l < _offsets.size(); ++l)
90 {
91 for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
92 {
93 for (unsigned int dx = 0; dx < _offsets[l][dy].size(); ++dx)
94 {
95 uint64_t tileOffset = is.tellg();
96
97 if (isMultiPartFile)
98 {
99 int partNumber;
100 OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, partNumber);
101 }
102
103 int tileX;
104 OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, tileX);
105
106 int tileY;
107 OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, tileY);
108
109 int levelX;
110 OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, levelX);
111
112 int levelY;
113 OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, levelY);
114
115 if(isDeep)
116 {
117 uint64_t packed_offset_table_size;
118 uint64_t packed_sample_size;
119
120 OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, packed_offset_table_size);
121 OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, packed_sample_size);
122
123 // check for bad values to prevent overflow
124 if ( packed_offset_table_size < 0 ||
125 packed_sample_size < 0 ||
126 ( INT64_MAX - packed_offset_table_size < packed_sample_size) ||
127 ( INT64_MAX - (packed_offset_table_size + packed_sample_size) ) < 8 )
128 {
129 throw IEX_NAMESPACE::IoExc("Invalid deep tile size");
130 }
131
132 // next uint64_t is unpacked sample size - skip that too
133 Xdr::skip <StreamIO> (is, packed_offset_table_size+packed_sample_size+8);
134
135 }
136 else
137 {
138 int dataSize;
139 OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, dataSize);
140
141 // check for bad values to prevent overflow
142 if ( dataSize < 0 )
143 {
144 throw IEX_NAMESPACE::IoExc("Invalid tile size");
145 }
146
147 Xdr::skip <StreamIO> (is, dataSize);
148 }
149 if (skipOnly) continue;
150
151 if (!isValidTile(tileX, tileY, levelX, levelY))
152 return;
153
154 operator () (tileX, tileY, levelX, levelY) = tileOffset;
155 }
156 }
157 }
158 }
159
160
161 void
reconstructFromFile(OPENEXR_IMF_INTERNAL_NAMESPACE::IStream & is,bool isMultiPart,bool isDeep)162 TileOffsets::reconstructFromFile (OPENEXR_IMF_INTERNAL_NAMESPACE::IStream &is,bool isMultiPart,bool isDeep)
163 {
164 //
165 // Try to reconstruct a missing tile offset table by sequentially
166 // scanning through the file, and recording the offsets in the file
167 // of the tiles we find.
168 //
169
170 uint64_t position = is.tellg();
171
172 try
173 {
174 findTiles (is,isMultiPart,isDeep,false);
175 }
176 catch (...) //NOSONAR - suppress vulnerability reports from SonarCloud.
177 {
178 //
179 // Suppress all exceptions. This function is called only to
180 // reconstruct the tile offset table for incomplete files,
181 // and exceptions are likely.
182 //
183 }
184
185 is.clear();
186 is.seekg (position);
187 }
188
189
190 void
readFrom(OPENEXR_IMF_INTERNAL_NAMESPACE::IStream & is,bool & complete,bool isMultiPartFile,bool isDeep)191 TileOffsets::readFrom (OPENEXR_IMF_INTERNAL_NAMESPACE::IStream &is, bool &complete,bool isMultiPartFile, bool isDeep)
192 {
193 //
194 // Read in the tile offsets from the file's tile offset table
195 //
196
197 for (unsigned int l = 0; l < _offsets.size(); ++l)
198 for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
199 for (unsigned int dx = 0; dx < _offsets[l][dy].size(); ++dx)
200 OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::read <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (is, _offsets[l][dy][dx]);
201
202 //
203 // Check if any tile offsets are invalid.
204 //
205 // Invalid offsets mean that the file is probably incomplete
206 // (the offset table is the last thing written to the file).
207 // Either some process is still busy writing the file, or
208 // writing the file was aborted.
209 //
210 // We should still be able to read the existing parts of the
211 // file. In order to do this, we have to make a sequential
212 // scan over the scan tile to reconstruct the tile offset
213 // table.
214 //
215
216 if (anyOffsetsAreInvalid())
217 {
218 complete = false;
219 reconstructFromFile (is,isMultiPartFile,isDeep);
220 }
221 else
222 {
223 complete = true;
224 }
225
226 }
227
228
229 void
readFrom(std::vector<uint64_t> chunkOffsets,bool & complete)230 TileOffsets::readFrom (std::vector<uint64_t> chunkOffsets,bool &complete)
231 {
232 size_t totalSize = 0;
233
234 for (unsigned int l = 0; l < _offsets.size(); ++l)
235 for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
236 totalSize += _offsets[l][dy].size();
237
238 if (chunkOffsets.size() != totalSize)
239 throw IEX_NAMESPACE::ArgExc ("Wrong offset count, not able to read from this array");
240
241
242
243 int pos = 0;
244 for (size_t l = 0; l < _offsets.size(); ++l)
245 for (size_t dy = 0; dy < _offsets[l].size(); ++dy)
246 for (size_t dx = 0; dx < _offsets[l][dy].size(); ++dx)
247 {
248 _offsets[l][dy][dx] = chunkOffsets[pos];
249 pos++;
250 }
251
252 complete = !anyOffsetsAreInvalid();
253
254 }
255
256
257 uint64_t
writeTo(OPENEXR_IMF_INTERNAL_NAMESPACE::OStream & os) const258 TileOffsets::writeTo (OPENEXR_IMF_INTERNAL_NAMESPACE::OStream &os) const
259 {
260 //
261 // Write the tile offset table to the file, and
262 // return the position of the start of the table
263 // in the file.
264 //
265
266 uint64_t pos = os.tellp();
267
268 if (pos == static_cast<uint64_t>(-1))
269 IEX_NAMESPACE::throwErrnoExc ("Cannot determine current file position (%T).");
270
271 for (unsigned int l = 0; l < _offsets.size(); ++l)
272 for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
273 for (unsigned int dx = 0; dx < _offsets[l][dy].size(); ++dx)
274 OPENEXR_IMF_INTERNAL_NAMESPACE::Xdr::write <OPENEXR_IMF_INTERNAL_NAMESPACE::StreamIO> (os, _offsets[l][dy][dx]);
275
276 return pos;
277 }
278
279 namespace {
280 struct tilepos{
281 uint64_t filePos;
282 int dx;
283 int dy;
284 int l;
operator <__anonf84dcb2d0111::tilepos285 bool operator <(const tilepos & other) const
286 {
287 return filePos < other.filePos;
288 }
289 };
290 }
291 //-------------------------------------
292 // fill array with tile coordinates in the order they appear in the file
293 //
294 // each input array must be of size (totalTiles)
295 //
296 //
297 // if the tile order is not RANDOM_Y, it is more efficient to compute the
298 // tile ordering rather than using this function
299 //
300 //-------------------------------------
getTileOrder(int dx_table[],int dy_table[],int lx_table[],int ly_table[]) const301 void TileOffsets::getTileOrder(int dx_table[],int dy_table[],int lx_table[],int ly_table[]) const
302 {
303 //
304 // helper class
305 //
306
307 // how many entries?
308 size_t entries=0;
309 for (unsigned int l = 0; l < _offsets.size(); ++l)
310 for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
311 entries+=_offsets[l][dy].size();
312
313 std::vector<struct tilepos> table(entries);
314
315 size_t i = 0;
316 for (unsigned int l = 0; l < _offsets.size(); ++l)
317 for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
318 for (unsigned int dx = 0; dx < _offsets[l][dy].size(); ++dx)
319 {
320 table[i].filePos = _offsets[l][dy][dx];
321 table[i].dx = dx;
322 table[i].dy = dy;
323 table[i].l = l;
324
325 ++i;
326
327 }
328
329 std::sort(table.begin(),table.end());
330
331 //
332 // write out the values
333 //
334
335 // pass 1: write out dx and dy, since these are independent of level mode
336
337 for(size_t i=0;i<entries;i++)
338 {
339 dx_table[i] = table[i].dx;
340 dy_table[i] = table[i].dy;
341 }
342
343 // now write out the levels, which depend on the level mode
344
345 switch (_mode)
346 {
347 case ONE_LEVEL:
348 {
349 for(size_t i=0;i<entries;i++)
350 {
351 lx_table[i] = 0;
352 ly_table[i] = 0;
353 }
354 break;
355 }
356 case MIPMAP_LEVELS:
357 {
358 for(size_t i=0;i<entries;i++)
359 {
360 lx_table[i]= table[i].l;
361 ly_table[i] =table[i].l;
362
363 }
364 break;
365 }
366
367 case RIPMAP_LEVELS:
368 {
369 for(size_t i=0;i<entries;i++)
370 {
371 lx_table[i]= table[i].l % _numXLevels;
372 ly_table[i] = table[i].l / _numXLevels;
373
374 }
375 break;
376 }
377 case NUM_LEVELMODES :
378 throw IEX_NAMESPACE::LogicExc("Bad level mode getting tile order");
379 }
380
381
382
383 }
384
385
386 bool
isEmpty() const387 TileOffsets::isEmpty () const
388 {
389 for (unsigned int l = 0; l < _offsets.size(); ++l)
390 for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
391 for (unsigned int dx = 0; dx < _offsets[l][dy].size(); ++dx)
392 if (_offsets[l][dy][dx] != 0)
393 return false;
394 return true;
395 }
396
397
398 bool
isValidTile(int dx,int dy,int lx,int ly) const399 TileOffsets::isValidTile (int dx, int dy, int lx, int ly) const
400 {
401 if(lx<0 || ly < 0 || dx<0 || dy < 0) return false;
402 switch (_mode)
403 {
404 case ONE_LEVEL:
405
406 if (lx == 0 &&
407 ly == 0 &&
408 _offsets.size() > 0 &&
409 int(_offsets[0].size()) > dy &&
410 int(_offsets[0][dy].size()) > dx)
411 {
412 return true;
413 }
414
415 break;
416
417 case MIPMAP_LEVELS:
418
419 if (lx < _numXLevels &&
420 ly < _numYLevels &&
421 int(_offsets.size()) > lx &&
422 int(_offsets[lx].size()) > dy &&
423 int(_offsets[lx][dy].size()) > dx)
424 {
425 return true;
426 }
427
428 break;
429
430 case RIPMAP_LEVELS:
431
432 if (lx < _numXLevels &&
433 ly < _numYLevels &&
434 (_offsets.size() > (size_t) lx+ ly * (size_t) _numXLevels) &&
435 int(_offsets[lx + ly * _numXLevels].size()) > dy &&
436 int(_offsets[lx + ly * _numXLevels][dy].size()) > dx)
437 {
438 return true;
439 }
440
441 break;
442
443 default:
444
445 return false;
446 }
447
448 return false;
449 }
450
451
452 uint64_t &
operator ()(int dx,int dy,int lx,int ly)453 TileOffsets::operator () (int dx, int dy, int lx, int ly)
454 {
455 //
456 // Looks up the value of the tile with tile coordinate (dx, dy)
457 // and level number (lx, ly) in the _offsets array, and returns
458 // the cooresponding offset.
459 //
460
461 switch (_mode)
462 {
463 case ONE_LEVEL:
464
465 return _offsets[0][dy][dx];
466
467 case MIPMAP_LEVELS:
468
469 return _offsets[lx][dy][dx];
470
471 case RIPMAP_LEVELS:
472
473 return _offsets[lx + ly * _numXLevels][dy][dx];
474
475 default:
476
477 throw IEX_NAMESPACE::ArgExc ("Unknown LevelMode format.");
478 }
479 }
480
481
482 uint64_t &
operator ()(int dx,int dy,int l)483 TileOffsets::operator () (int dx, int dy, int l)
484 {
485 return operator () (dx, dy, l, l);
486 }
487
488
489 const uint64_t &
operator ()(int dx,int dy,int lx,int ly) const490 TileOffsets::operator () (int dx, int dy, int lx, int ly) const
491 {
492 //
493 // Looks up the value of the tile with tile coordinate (dx, dy)
494 // and level number (lx, ly) in the _offsets array, and returns
495 // the cooresponding offset.
496 //
497
498 switch (_mode)
499 {
500 case ONE_LEVEL:
501
502 return _offsets[0][dy][dx];
503
504 case MIPMAP_LEVELS:
505
506 return _offsets[lx][dy][dx];
507
508 case RIPMAP_LEVELS:
509
510 return _offsets[lx + ly * _numXLevels][dy][dx];
511
512 default:
513
514 throw IEX_NAMESPACE::ArgExc ("Unknown LevelMode format.");
515 }
516 }
517
518
519 const uint64_t &
operator ()(int dx,int dy,int l) const520 TileOffsets::operator () (int dx, int dy, int l) const
521 {
522 return operator () (dx, dy, l, l);
523 }
524
525 const std::vector<std::vector<std::vector <uint64_t> > >&
getOffsets() const526 TileOffsets::getOffsets() const
527 {
528 return _offsets;
529 }
530
531
532 OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT
533