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