1 // -*- mode: C++; tab-width: 4 -*-
2 // vi: ts=4
3
4 /*
5 * Copyright (c) 2010, Patrick A. Palmer and Leszek Godlewski.
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
10 *
11 * - Redistributions of source code must retain the above copyright notice,
12 * this list of conditions and the following disclaimer.
13 *
14 * - Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * - Neither the name of Patrick A. Palmer nor the names of its
19 * contributors may be used to endorse or promote products derived from
20 * this software without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 * POSSIBILITY OF SUCH DAMAGE.
33 */
34
35
36 #include <cassert>
37 #include <cstdio>
38 #include <cstdlib>
39 #include <cstring>
40 #include <ctime>
41 #include <limits>
42
43 #include <OpenImageIO/strutil.h>
44 #include <OpenImageIO/sysutil.h>
45
46 #include "CineonHeader.h"
47 #include "EndianSwap.h"
48
49
50
51 // shortcut macros
52 #define EmptyString(x) memset(x, 0, sizeof(x))
53 #define EmptyFloat(x) x = std::numeric_limits<R32>::infinity()
54 #define EmptyVector(x) (EmptyFloat((x)[0]), \
55 EmptyFloat((x)[1]))
56
57
58 namespace cineon {
59
Hex(char x)60 char Hex(char x)
61 {
62 if (x >= 10)
63 return (x-10+'A');
64 else
65 return (x+'0');
66 }
67
68
69
Header()70 cineon::Header::Header() : GenericHeader(), IndustryHeader()
71 {
72 }
73
74
75
GenericHeader()76 cineon::GenericHeader::GenericHeader()
77 {
78 this->Reset();
79 }
80
81
Reset()82 void cineon::GenericHeader::Reset()
83 {
84 // File Information
85 this->magicNumber = MAGIC_COOKIE;
86 this->imageOffset = ~0;
87 EmptyString(this->version);
88 OIIO::Strutil::safe_strcpy(this->version, SPEC_VERSION, sizeof(this->version));
89 fileSize = sizeof(cineon::Header);
90
91 // genericSize is the size of the file/image/orientation headers
92 // sizeof(cineon::GenericHeader) won't give the correct results because
93 // of compiler padding
94 this->genericSize = 1024;
95
96 // industrySize is the size of the motion picture/television headers
97 this->industrySize = 1024;
98
99 this->userSize = 0;
100 EmptyString(this->fileName);
101 EmptyString(this->creationDate);
102 EmptyString(this->creationTime);
103 EmptyString(this->reserved1);
104
105 // Image Information
106 this->imageOrientation = kUndefinedOrientation;
107 this->numberOfElements = 0xff;
108 this->unused1[0] = this->unused1[1] = 0xff;
109 EmptyVector(this->whitePoint);
110 EmptyVector(this->redPrimary);
111 EmptyVector(this->greenPrimary);
112 EmptyVector(this->bluePrimary);
113 EmptyString(this->labelText);
114 EmptyString(this->reserved2);
115 this->interleave = 0xff;
116 this->packing = 0xff;
117 this->dataSign = 0xff;
118 this->imageSense = 0xff;
119 this->endOfLinePadding = 0xffffffff;
120 this->endOfImagePadding = 0xffffffff;
121
122 // Image Orientation
123 this->xOffset = this->yOffset = 0xffffffff;
124 EmptyString(this->sourceImageFileName);
125 EmptyString(this->sourceDate);
126 EmptyString(this->sourceTime);
127 EmptyString(this->inputDevice);
128 EmptyString(this->inputDeviceModelNumber);
129 EmptyString(this->inputDeviceSerialNumber);
130 EmptyFloat(this->xDevicePitch);
131 EmptyFloat(this->yDevicePitch);
132 EmptyFloat(this->gamma);
133 EmptyString(this->reserved3);
134 EmptyString(this->reserved4);
135 }
136
137
IndustryHeader()138 cineon::IndustryHeader::IndustryHeader()
139 {
140 this->Reset();
141 }
142
143
Reset()144 void cineon::IndustryHeader::Reset()
145 {
146 // Motion Picture Industry Specific
147 this->filmManufacturingIdCode = 0xFF;
148 this->filmType = 0xFF;
149 this->perfsOffset = 0xFF;
150 this->prefix = 0xFFFFFFFF;
151 this->count = 0xFFFFFFFF;
152 EmptyString(this->format);
153 this->framePosition = 0xffffffff;
154 EmptyFloat(this->frameRate);
155 EmptyString(this->frameId);
156 EmptyString(this->slateInfo);
157 EmptyString(this->reserved1);
158 }
159
160
ImageElement()161 cineon::ImageElement::ImageElement()
162 {
163 this->lowData = R32(0xffffffff);
164 this->lowQuantity = R32(0xffffffff);
165 this->highData = R32(0xffffffff);
166 this->highQuantity = R32(0xffffffff);
167 this->bitDepth = 0xff;
168 }
169
170
Read(InStream * io)171 bool cineon::Header::Read(InStream *io)
172 {
173 // rewind file
174 io->Rewind();
175
176 // read in the header from the file
177 size_t r = sizeof(GenericHeader) + sizeof(IndustryHeader);
178 if (io->Read(&(this->magicNumber), r) != r)
179 return false;
180
181 // validate
182 return this->Validate();
183 }
184
185
186 // Check to see if the compiler placed the data members in the expected memory offsets
187
Check()188 bool cineon::Header::Check()
189 {
190 // genericSize is the size of the file/image/orientation headers
191 // sizeof(cineon::GenericHeader) won't give the correct results because
192 // of compiler padding
193 // file header is 768 bytes
194 // image header is 640 bytes
195 // orientation header 256 bytes
196
197 if (sizeof(GenericHeader) != (768 + 640 + 256))
198 return false;
199
200 // industrySize is the size of the motion picture/television headers
201 // motion picture header is 256 bytes
202 // television header is 128 bytes
203 if (sizeof(IndustryHeader) != (256 + 128))
204 return false;
205
206 // data size checks
207 if (sizeof(U8) != 1 || sizeof(U16) != 2 || sizeof(U32) != 4 || sizeof(R32) != 4 || sizeof(R64) != 8)
208 return false;
209
210 return true;
211 }
212
213
214
Write(OutStream * io)215 bool cineon::Header::Write(OutStream *io)
216 {
217 // write the header to the file
218 size_t r = sizeof(GenericHeader) + sizeof(IndustryHeader);
219 if (io->Write(&(this->magicNumber), r) != r)
220 return false;
221 return true;
222 }
223
224
WriteOffsetData(OutStream * io)225 bool cineon::Header::WriteOffsetData(OutStream *io)
226 {
227 // calculate the number of elements
228 this->CalculateNumberOfElements();
229
230 // write the image offset
231 const long FIELD2 = 4; // offset to image in header
232 if (io->Seek(FIELD2, OutStream::kStart) == false)
233 return false;
234 if (io->Write(&this->imageOffset, sizeof(U32)) == false)
235 return false;
236
237
238 // write the file size
239 const long FIELD4 = 16; // offset to total image file size in header
240 if (io->Seek(FIELD4, OutStream::kStart) == false)
241 return false;
242 if (io->Write(&this->fileSize, sizeof(U32)) == false)
243 return false;
244
245 // write the number of elements
246 const long FIELD19 = 770; // offset to number of image elements in header
247 if (io->Seek(FIELD19, OutStream::kStart) == false)
248 return false;
249 if (io->Write(&this->numberOfElements, sizeof(U16)) == false)
250 return false;
251
252 // write the image offsets
253 //const long FIELD21_12 = 808; // offset to image offset in image element data structure
254 //const long IMAGE_STRUCTURE = 72; // sizeof the image data structure
255
256 /*int i;
257 for (i = 0; i < MAX_ELEMENTS; i++)
258 {
259 // only write if there is a defined image description
260 if (this->chan[i].descriptor == kUndefinedDescriptor)
261 continue;
262
263 // seek to the image offset entry in each image element
264 if (io->Seek((FIELD21_12 + (IMAGE_STRUCTURE * i)), OutStream::kStart) == false)
265 return false;
266
267 // write
268 if (io->Write(&this->chan[i].dataOffset, sizeof(U32)) == false)
269 return false;
270
271 }*/
272
273 return true;
274 }
275
276
ValidMagicCookie(const U32 magic)277 bool cineon::Header::ValidMagicCookie(const U32 magic)
278 {
279 U32 mc = MAGIC_COOKIE;
280
281 if (magic == mc)
282 return true;
283 else if (magic == SwapBytes(mc))
284 return true;
285 else
286 return false;
287 }
288
289
DetermineByteSwap(const U32 magic) const290 bool cineon::Header::DetermineByteSwap(const U32 magic) const
291 {
292 U32 mc = MAGIC_COOKIE;
293
294 bool byteSwap = false;
295
296 if (magic != mc)
297 byteSwap = true;
298
299 return byteSwap;
300 }
301
302
Validate()303 bool cineon::Header::Validate()
304 {
305 // check magic cookie
306 if (!this->ValidMagicCookie(this->magicNumber))
307 return false;
308
309 // determine if bytes needs to be swapped around
310 if (this->DetermineByteSwap(this->magicNumber))
311 {
312 // File information
313 SwapBytes(this->imageOffset);
314 SwapBytes(this->genericSize);
315 SwapBytes(this->industrySize);
316 SwapBytes(this->userSize);
317 SwapBytes(this->fileSize);
318
319 // Image information
320 for (int i = 0; i < MAX_ELEMENTS; i++)
321 {
322 SwapBytes(this->chan[i].pixelsPerLine);
323 SwapBytes(this->chan[i].linesPerElement);
324 SwapBytes(this->chan[i].lowData);
325 SwapBytes(this->chan[i].lowQuantity);
326 SwapBytes(this->chan[i].highData);
327 SwapBytes(this->chan[i].highQuantity);
328 SwapBytes(this->chan[i].bitDepth);
329 }
330 SwapBytes(this->whitePoint[0]);
331 SwapBytes(this->whitePoint[1]);
332 SwapBytes(this->redPrimary[0]);
333 SwapBytes(this->redPrimary[1]);
334 SwapBytes(this->greenPrimary[0]);
335 SwapBytes(this->greenPrimary[1]);
336 SwapBytes(this->bluePrimary[0]);
337 SwapBytes(this->bluePrimary[1]);
338 SwapBytes(this->endOfLinePadding);
339 SwapBytes(this->endOfImagePadding);
340
341
342 // Image Origination information
343 SwapBytes(this->xOffset);
344 SwapBytes(this->yOffset);
345 SwapBytes(this->xDevicePitch);
346 SwapBytes(this->yDevicePitch);
347 SwapBytes(this->gamma);
348
349
350 // Motion Picture Industry Specific
351 SwapBytes(this->prefix);
352 SwapBytes(this->count);
353 SwapBytes(this->framePosition);
354 SwapBytes(this->frameRate);
355
356 }
357
358 return true;
359 }
360
361
362
Reset()363 void cineon::Header::Reset()
364 {
365 GenericHeader::Reset();
366 IndustryHeader::Reset();
367 }
368
369
370
ImageElementCount() const371 int cineon::GenericHeader::ImageElementCount() const
372 {
373 int i = 0;
374
375 while (i < MAX_ELEMENTS )
376 {
377 if (this->ImageDescriptor(i) == kUndefinedDescriptor)
378 break;
379 i++;
380 }
381
382 return i;
383 }
384
385
CalculateNumberOfElements()386 void cineon::GenericHeader::CalculateNumberOfElements()
387 {
388 int i = this->ImageElementCount();
389
390 if (i == 0)
391 this->numberOfElements = 0xff;
392 else
393 this->numberOfElements = U8(i);
394 }
395
396
CalculateOffsets()397 void cineon::Header::CalculateOffsets()
398 {
399 int i;
400
401 for (i = 0; i < MAX_ELEMENTS; i++)
402 {
403 // only write if there is a defined image description
404 if (this->chan[i].designator[1] == kUndefinedDescriptor)
405 continue;
406
407
408 }
409 }
410
411
ComponentDataSize(const int element) const412 cineon::DataSize cineon::GenericHeader::ComponentDataSize(const int element) const
413 {
414 if (element < 0 || element >= MAX_ELEMENTS)
415 return kByte;
416
417 cineon::DataSize ret;
418
419 switch (this->chan[element].bitDepth)
420 {
421 case 8:
422 ret = kByte;
423 break;
424 case 10:
425 case 12:
426 case 16:
427 ret = kWord;
428 break;
429 case 32:
430 ret = kInt;
431 break;
432 case 64:
433 ret = kLongLong;
434 break;
435 default:
436 assert(0 && "Unknown bit depth");
437 ret = kLongLong;
438 break;
439 }
440
441 return ret;
442 }
443
444
ComponentByteCount(const int element) const445 int cineon::GenericHeader::ComponentByteCount(const int element) const
446 {
447 if (element < 0 || element >= MAX_ELEMENTS)
448 return kByte;
449
450 int ret;
451
452 switch (this->chan[element].bitDepth)
453 {
454 case 8:
455 ret = sizeof(U8);
456 break;
457 case 10:
458 case 12:
459 case 16:
460 ret = sizeof(U16);
461 break;
462 case 32:
463 ret = sizeof(R32);
464 break;
465 case 64:
466 ret = sizeof(R64);
467 break;
468 default:
469 assert(0 && "Unknown bit depth");
470 ret = sizeof(R64);
471 break;
472 }
473
474 return ret;
475 }
476
477
DataSizeByteCount(const DataSize ds)478 int cineon::GenericHeader::DataSizeByteCount(const DataSize ds)
479 {
480
481 int ret = 0;
482
483 switch (ds)
484 {
485 case kByte:
486 ret = sizeof(U8);
487 break;
488 case kWord:
489 ret = sizeof(U16);
490 break;
491 case kInt:
492 ret = sizeof(U32);
493 break;
494 case kLongLong:
495 ret = sizeof(U64);
496 break;
497 }
498
499 return ret;
500 }
501
502
FilmEdgeCode(char * edge) const503 void cineon::IndustryHeader::FilmEdgeCode(char *edge) const
504 {
505 if (this->filmManufacturingIdCode == 0xff
506 && this->filmType == 0xff
507 && this->perfsOffset == 0xff
508 && this->prefix == 0xffffffff
509 && this->count == 0xffffffff)
510 *edge = 0;
511 else
512 sprintf(edge, "%02u%02u%02u%06u%04u",
513 (unsigned int)this->filmManufacturingIdCode,
514 (unsigned int)this->filmType,
515 (unsigned int)this->perfsOffset,
516 this->prefix,
517 this->count);
518 }
519
520
SetFilmEdgeCode(const char * edge)521 void cineon::IndustryHeader::SetFilmEdgeCode(const char *edge)
522 {
523 char buf[7];
524
525 strncpy(buf, edge, 2);
526 this->filmManufacturingIdCode = OIIO::Strutil::stoi(buf);
527
528 strncpy(buf, edge + 2, 2);
529 this->filmType = OIIO::Strutil::stoi(buf);
530
531 strncpy(buf, edge + 4, 2);
532 this->perfsOffset = OIIO::Strutil::stoi(buf);
533
534 strncpy(buf, edge + 6, 6);
535 this->prefix = OIIO::Strutil::stoi(buf);
536
537 strncpy(buf, edge + 12, 4);
538 this->count = OIIO::Strutil::stoi(buf);
539 }
540
541
SetCreationTimeDate(const long sec)542 void cineon::GenericHeader::SetCreationTimeDate(const long sec)
543 {
544 char str[32];
545
546 #ifdef _WIN32
547 _tzset();
548 #endif
549
550 const time_t t = time_t(sec);
551 struct tm localtm;
552 OIIO::Sysutil::get_local_time(&t, &localtm);
553 ::strftime(str, 32, "%Y:%m:%d:%H:%M:%S%Z", &localtm);
554 OIIO::Strutil::safe_strcpy(this->creationDate, str, 11);
555 OIIO::Strutil::safe_strcpy(this->creationTime, str + 11, 12);
556 }
557
558
SetSourceTimeDate(const long sec)559 void cineon::GenericHeader::SetSourceTimeDate(const long sec)
560 {
561 char str[32];
562
563 #ifdef _WIN32
564 _tzset();
565 #endif
566
567 const time_t t = time_t(sec);
568 struct tm localtm;
569 OIIO::Sysutil::get_local_time(&t, &localtm);
570 ::strftime(str, 32, "%Y:%m:%d:%H:%M:%S%Z", &localtm);
571 OIIO::Strutil::safe_strcpy(this->sourceDate, str, 11);
572 OIIO::Strutil::safe_strcpy(this->sourceTime, str + 11, 12);
573 }
574
575
576
577 // Height()
578 // this function determines the height of the image taking in account for the image orientation
579 // if an image is 1920x1080 but is oriented top to bottom, left to right then the height stored
580 // in the image is 1920 rather than 1080
581
Height() const582 cineon::U32 cineon::Header::Height() const
583 {
584 U32 h = 0;
585
586 for (int i = 0; i < this->NumberOfElements(); i++) {
587 switch (this->ImageOrientation())
588 {
589 case kTopToBottomLeftToRight:
590 case kTopToBottomRightToLeft:
591 case kBottomToTopLeftToRight:
592 case kBottomToTopRightToLeft:
593 if (this->PixelsPerLine(i) > h)
594 h = this->PixelsPerLine(i);
595 break;
596 default:
597 if (this->LinesPerElement(i) > h)
598 h = this->LinesPerElement(i);
599 break;
600 }
601 }
602
603 return h;
604 }
605
606
607 // Width()
608 // this function determines the width of the image taking in account for the image orientation
609 // if an image is 1920x1080 but is oriented top to bottom, left to right then the width stored
610 // in the image is 1920 rather than 1080
611
Width() const612 cineon::U32 cineon::Header::Width() const
613 {
614 U32 w = 0;
615
616 for (int i = 0; i < this->NumberOfElements(); i++) {
617 switch (this->ImageOrientation())
618 {
619 case kTopToBottomLeftToRight:
620 case kTopToBottomRightToLeft:
621 case kBottomToTopLeftToRight:
622 case kBottomToTopRightToLeft:
623 if (this->LinesPerElement(i) > w)
624 w = this->LinesPerElement(i);
625 break;
626 default:
627 if (this->PixelsPerLine(i) > w)
628 w = this->PixelsPerLine(i);
629 break;
630 }
631 }
632
633 return w;
634 }
635
636 }
637
638