Home | History | Annotate | Download | only in source
      1 /*****************************************************************************/
      2 // Copyright 2006-2008 Adobe Systems Incorporated
      3 // All Rights Reserved.
      4 //
      5 // NOTICE:  Adobe permits you to use, modify, and distribute this file in
      6 // accordance with the terms of the Adobe license agreement accompanying it.
      7 /*****************************************************************************/
      8 
      9 /* $Id: //mondo/dng_sdk_1_4/dng_sdk/source/dng_iptc.cpp#1 $ */
     10 /* $DateTime: 2012/05/30 13:28:51 $ */
     11 /* $Change: 832332 $ */
     12 /* $Author: tknoll $ */
     13 
     14 /*****************************************************************************/
     15 
     16 #include "dng_iptc.h"
     17 
     18 #include "dng_assertions.h"
     19 #include "dng_auto_ptr.h"
     20 #include "dng_memory_stream.h"
     21 #include "dng_stream.h"
     22 #include "dng_utils.h"
     23 
     24 /*****************************************************************************/
     25 
     26 dng_iptc::dng_iptc ()
     27 
     28 	:	fTitle ()
     29 
     30 	,	fUrgency (-1)
     31 
     32 	,	fCategory ()
     33 
     34 	,	fSupplementalCategories ()
     35 
     36 	,	fKeywords ()
     37 
     38 	,	fInstructions ()
     39 
     40 	,	fDateTimeCreated ()
     41 
     42 	,	fDigitalCreationDateTime ()
     43 
     44 	,	fAuthors         ()
     45 	,	fAuthorsPosition ()
     46 
     47 	,	fCity        ()
     48 	,	fState       ()
     49 	,	fCountry     ()
     50 	,	fCountryCode ()
     51 
     52 	,	fLocation ()
     53 
     54 	,	fTransmissionReference ()
     55 
     56 	,	fHeadline ()
     57 
     58 	,	fCredit ()
     59 
     60 	,	fSource ()
     61 
     62 	,	fCopyrightNotice ()
     63 
     64 	,	fDescription       ()
     65 	,	fDescriptionWriter ()
     66 
     67 	{
     68 
     69 	}
     70 
     71 /*****************************************************************************/
     72 
     73 dng_iptc::~dng_iptc ()
     74 	{
     75 
     76 	}
     77 
     78 /*****************************************************************************/
     79 
     80 bool dng_iptc::IsEmpty () const
     81 	{
     82 
     83 	if (fTitle.NotEmpty ())
     84 		{
     85 		return false;
     86 		}
     87 
     88 	if (fUrgency >= 0)
     89 		{
     90 		return false;
     91 		}
     92 
     93 	if (fCategory.NotEmpty ())
     94 		{
     95 		return false;
     96 		}
     97 
     98 	if (fSupplementalCategories.Count () > 0)
     99 		{
    100 		return false;
    101 		}
    102 
    103 	if (fKeywords.Count () > 0)
    104 		{
    105 		return false;
    106 		}
    107 
    108 	if (fInstructions.NotEmpty ())
    109 		{
    110 		return false;
    111 		}
    112 
    113 	if (fDateTimeCreated.IsValid ())
    114 		{
    115 		return false;
    116 		}
    117 
    118 	if (fDigitalCreationDateTime.IsValid ())
    119 		{
    120 		return false;
    121 		}
    122 
    123 	if (fAuthors.Count () != 0 ||
    124 		fAuthorsPosition.NotEmpty ())
    125 		{
    126 		return false;
    127 		}
    128 
    129 	if (fCity   .NotEmpty () ||
    130 		fState  .NotEmpty () ||
    131 		fCountry.NotEmpty ())
    132 		{
    133 		return false;
    134 		}
    135 
    136 	if (fCountryCode.NotEmpty ())
    137 		{
    138 		return false;
    139 		}
    140 
    141 	if (fLocation.NotEmpty ())
    142 		{
    143 		return false;
    144 		}
    145 
    146 	if (fTransmissionReference.NotEmpty ())
    147 		{
    148 		return false;
    149 		}
    150 
    151 	if (fHeadline.NotEmpty ())
    152 		{
    153 		return false;
    154 		}
    155 
    156 	if (fCredit.NotEmpty ())
    157 		{
    158 		return false;
    159 		}
    160 
    161 	if (fSource.NotEmpty ())
    162 		{
    163 		return false;
    164 		}
    165 
    166 	if (fCopyrightNotice.NotEmpty ())
    167 		{
    168 		return false;
    169 		}
    170 
    171 	if (fDescription      .NotEmpty () ||
    172 		fDescriptionWriter.NotEmpty ())
    173 		{
    174 		return false;
    175 		}
    176 
    177 	return true;
    178 
    179 	}
    180 
    181 /*****************************************************************************/
    182 
    183 void dng_iptc::ParseString (dng_stream &stream,
    184 						    dng_string &s,
    185 						    CharSet charSet)
    186 	{
    187 
    188 	uint32 length = stream.Get_uint16 ();
    189 
    190 	dng_memory_data buffer (length + 1);
    191 
    192 	char *c = buffer.Buffer_char ();
    193 
    194 	stream.Get (c, length);
    195 
    196 	c [length] = 0;
    197 
    198 	switch (charSet)
    199 		{
    200 
    201 		case kCharSetUTF8:
    202 			{
    203 			s.Set_UTF8 (c);
    204 			break;
    205 			}
    206 
    207 		default:
    208 			{
    209 			s.Set_SystemEncoding (c);
    210 			}
    211 
    212 		}
    213 
    214 	s.SetLineEndingsToNewLines ();
    215 
    216 	s.StripLowASCII ();
    217 
    218 	s.TrimTrailingBlanks ();
    219 
    220 	}
    221 
    222 /*****************************************************************************/
    223 
    224 void dng_iptc::Parse (const void *blockData,
    225 					  uint32 blockSize,
    226 					  uint64 offsetInOriginalFile)
    227 	{
    228 
    229 	dng_stream stream (blockData,
    230 					   blockSize,
    231 					   offsetInOriginalFile);
    232 
    233 	stream.SetBigEndian ();
    234 
    235 	// Make a first pass though the data, trying to figure out the
    236 	// character set.
    237 
    238 	CharSet charSet = kCharSetUnknown;
    239 
    240 	bool isValidUTF8 = true;
    241 
    242 	bool hasEncodingMarker = false;
    243 
    244 	uint64 firstOffset = stream.Position ();
    245 
    246 	uint64 nextOffset = firstOffset;
    247 
    248 	while (nextOffset + 5 < stream.Length ())
    249 		{
    250 
    251 		stream.SetReadPosition (nextOffset);
    252 
    253 		uint8 firstByte = stream.Get_uint8 ();
    254 
    255 		if (firstByte != 0x1C) break;
    256 
    257 		uint8  record   = stream.Get_uint8  ();
    258 		uint8  dataSet  = stream.Get_uint8  ();
    259 		uint32 dataSize = stream.Get_uint16 ();
    260 
    261 		nextOffset = stream.Position () + dataSize;
    262 
    263 		if (record == 1)
    264 			{
    265 
    266 			switch (dataSet)
    267 				{
    268 
    269 				case 90:
    270 					{
    271 
    272 					hasEncodingMarker = true;
    273 
    274 					if (dataSize == 3)
    275 						{
    276 
    277 						uint32 byte1 = stream.Get_uint8 ();
    278 						uint32 byte2 = stream.Get_uint8 ();
    279 						uint32 byte3 = stream.Get_uint8 ();
    280 
    281 						if (byte1 == 27 /* Escape */ &&
    282 							byte2 == 0x25 &&
    283 							byte3 == 0x47)
    284 							{
    285 
    286 							charSet = kCharSetUTF8;
    287 
    288 							}
    289 
    290 						}
    291 
    292 					break;
    293 
    294 					}
    295 
    296 				default:
    297 					break;
    298 
    299 				}
    300 
    301 			}
    302 
    303 		else if (record == 2)
    304 			{
    305 
    306 			dng_memory_data buffer (dataSize + 1);
    307 
    308 			char *s = buffer.Buffer_char ();
    309 
    310 			stream.Get (s, dataSize);
    311 
    312 			s [dataSize] = 0;
    313 
    314 			isValidUTF8 = isValidUTF8 && dng_string::IsUTF8 (s);
    315 
    316 			}
    317 
    318 		}
    319 
    320 	// If we don't have an encoding marker, and the data is valid
    321 	// UTF-8, then assume that it is UTF-8 (rather than system encoding).
    322 
    323 	if (!hasEncodingMarker && isValidUTF8)
    324 		{
    325 
    326 		charSet = kCharSetUTF8;
    327 
    328 		}
    329 
    330 	// Make a second pass though the data, actually reading the data.
    331 
    332 	nextOffset = firstOffset;
    333 
    334 	while (nextOffset + 5 < stream.Length ())
    335 		{
    336 
    337 		stream.SetReadPosition (nextOffset);
    338 
    339 		uint8 firstByte = stream.Get_uint8 ();
    340 
    341 		if (firstByte != 0x1C) break;
    342 
    343 		uint8  record   = stream.Get_uint8  ();
    344 		uint8  dataSet  = stream.Get_uint8  ();
    345 		uint32 dataSize = stream.Get_uint16 ();
    346 
    347 		nextOffset = stream.Position () + dataSize;
    348 
    349 		if (record == 2)
    350 			{
    351 
    352 			stream.SetReadPosition (stream.Position () - 2);
    353 
    354 			switch ((DataSet) dataSet)
    355 				{
    356 
    357 				case kObjectNameSet:
    358 					{
    359 					ParseString (stream, fTitle, charSet);
    360 					break;
    361 					}
    362 
    363 				case kUrgencySet:
    364 					{
    365 
    366 					int32 size = stream.Get_uint16 ();
    367 
    368 					if (size == 1)
    369 						{
    370 
    371 						char c = stream.Get_int8 ();
    372 
    373 						if (c >= '0' && c <= '9')
    374 							{
    375 							fUrgency = c - '0';
    376 							}
    377 
    378 						}
    379 
    380 					break;
    381 
    382 					}
    383 
    384 				case kCategorySet:
    385 					{
    386 					ParseString (stream, fCategory, charSet);
    387 					break;
    388 					}
    389 
    390 				case kSupplementalCategoriesSet:
    391 					{
    392 
    393 					dng_string category;
    394 
    395 					ParseString (stream, category, charSet);
    396 
    397 					if (category.NotEmpty ())
    398 						{
    399 						fSupplementalCategories.Append (category);
    400 						}
    401 
    402 					break;
    403 
    404 					}
    405 
    406 				case kKeywordsSet:
    407 					{
    408 
    409 					dng_string keyword;
    410 
    411 					ParseString (stream, keyword, charSet);
    412 
    413 					if (keyword.NotEmpty ())
    414 						{
    415 						fKeywords.Append (keyword);
    416 						}
    417 
    418 					break;
    419 
    420 					}
    421 
    422 				case kSpecialInstructionsSet:
    423 					{
    424 					ParseString (stream, fInstructions, charSet);
    425 					break;
    426 					}
    427 
    428 				case kDateCreatedSet:
    429 					{
    430 
    431 					uint32 length = stream.Get_uint16 ();
    432 
    433 					if (length == 8)
    434 						{
    435 
    436 						char date [9];
    437 
    438 						stream.Get (date, 8);
    439 
    440 						date [8] = 0;
    441 
    442 						fDateTimeCreated.Decode_IPTC_Date (date);
    443 
    444 						}
    445 
    446 					break;
    447 
    448 					}
    449 
    450 				case kTimeCreatedSet:
    451 					{
    452 
    453 					uint32 length = stream.Get_uint16 ();
    454 
    455 					if (length >= 4 && length <= 11)
    456 						{
    457 
    458 						char time [12];
    459 
    460 						stream.Get (time, length);
    461 
    462 						time [length] = 0;
    463 
    464 						fDateTimeCreated.Decode_IPTC_Time (time);
    465 
    466 						}
    467 
    468 					break;
    469 
    470 					}
    471 
    472 				case kDigitalCreationDateSet:
    473 					{
    474 
    475 					uint32 length = stream.Get_uint16 ();
    476 
    477 					if (length == 8)
    478 						{
    479 
    480 						char date [9];
    481 
    482 						stream.Get (date, 8);
    483 
    484 						date [8] = 0;
    485 
    486 						fDigitalCreationDateTime.Decode_IPTC_Date (date);
    487 
    488 						}
    489 
    490 					break;
    491 
    492 					}
    493 
    494 				case kDigitalCreationTimeSet:
    495 					{
    496 
    497 					uint32 length = stream.Get_uint16 ();
    498 
    499 					if (length >= 4 && length <= 11)
    500 						{
    501 
    502 						char time [12];
    503 
    504 						stream.Get (time, length);
    505 
    506 						time [length] = 0;
    507 
    508 						fDigitalCreationDateTime.Decode_IPTC_Time (time);
    509 
    510 						}
    511 
    512 					break;
    513 
    514 					}
    515 
    516 				case kBylineSet:
    517 					{
    518 
    519 					dng_string author;
    520 
    521 					ParseString (stream, author, charSet);
    522 
    523 					if (author.NotEmpty ())
    524 						{
    525 						fAuthors.Append (author);
    526 						}
    527 
    528 					break;
    529 
    530 					}
    531 
    532 				case kBylineTitleSet:
    533 					{
    534 					ParseString (stream, fAuthorsPosition, charSet);
    535 					break;
    536 					}
    537 
    538 				case kCitySet:
    539 					{
    540 					ParseString (stream, fCity, charSet);
    541 					break;
    542 					}
    543 
    544 				case kProvinceStateSet:
    545 					{
    546 					ParseString (stream, fState, charSet);
    547 					break;
    548 					}
    549 
    550 				case kCountryNameSet:
    551 					{
    552 					ParseString (stream, fCountry, charSet);
    553 					break;
    554 					}
    555 
    556 				case kCountryCodeSet:
    557 					{
    558 					ParseString (stream, fCountryCode, charSet);
    559 					break;
    560 					}
    561 
    562 				case kSublocationSet:
    563 					{
    564 					ParseString (stream, fLocation, charSet);
    565 					break;
    566 					}
    567 
    568 				case kOriginalTransmissionReferenceSet:
    569 					{
    570 					ParseString (stream, fTransmissionReference, charSet);
    571 					break;
    572 					}
    573 
    574 				case kHeadlineSet:
    575 					{
    576 					ParseString (stream, fHeadline, charSet);
    577 					break;
    578 					}
    579 
    580 				case kCreditSet:
    581 					{
    582 					ParseString (stream, fCredit, charSet);
    583 					break;
    584 					}
    585 
    586 				case kSourceSet:
    587 					{
    588 					ParseString (stream, fSource, charSet);
    589 					break;
    590 					}
    591 
    592 				case kCopyrightNoticeSet:
    593 					{
    594 					ParseString (stream, fCopyrightNotice, charSet);
    595 					break;
    596 					}
    597 
    598 				case kCaptionSet:
    599 					{
    600 					ParseString (stream, fDescription, charSet);
    601 					break;
    602 					}
    603 
    604 				case kCaptionWriterSet:
    605 					{
    606 					ParseString (stream, fDescriptionWriter, charSet);
    607 					break;
    608 					}
    609 
    610 				// All other IPTC records are not part of the IPTC core
    611 				// and/or are not kept in sync with XMP tags, so we ignore
    612 				// them.
    613 
    614 				default:
    615 					break;
    616 
    617 				}
    618 
    619 			}
    620 
    621 		}
    622 
    623 	}
    624 
    625 /*****************************************************************************/
    626 
    627 void dng_iptc::SpoolString (dng_stream &stream,
    628 							const dng_string &s,
    629 							uint8 dataSet,
    630 							uint32 maxChars,
    631 							CharSet charSet)
    632 	{
    633 
    634 	if (s.IsEmpty ())
    635 		{
    636 		return;
    637 		}
    638 
    639 	stream.Put_uint16 (0x1C02);
    640 	stream.Put_uint8  (dataSet);
    641 
    642 	dng_string ss (s);
    643 
    644 	ss.SetLineEndingsToReturns ();
    645 
    646 	if (charSet == kCharSetUTF8)
    647 		{
    648 
    649 		// UTF-8 encoding.
    650 
    651 		if (ss.Length () > maxChars)
    652 			{
    653 			ss.Truncate (maxChars);
    654 			}
    655 
    656 		uint32 len = ss.Length ();
    657 
    658 		stream.Put_uint16 ((uint16) len);
    659 
    660 		stream.Put (ss.Get (), len);
    661 
    662 		}
    663 
    664 	else
    665 		{
    666 
    667 		// System character set encoding.
    668 
    669 		dng_memory_data buffer;
    670 
    671 		uint32 len = ss.Get_SystemEncoding (buffer);
    672 
    673 		if (len > maxChars)
    674 			{
    675 
    676 			uint32 lower = 0;
    677 			uint32 upper = ss.Length () - 1;
    678 
    679 			while (upper > lower)
    680 				{
    681 
    682 				uint32 middle = (upper + lower + 1) >> 1;
    683 
    684 				dng_string sss (ss);
    685 
    686 				sss.Truncate (middle);
    687 
    688 				len = sss.Get_SystemEncoding (buffer);
    689 
    690 				if (len <= maxChars)
    691 					{
    692 
    693 					lower = middle;
    694 
    695 					}
    696 
    697 				else
    698 					{
    699 
    700 					upper = middle - 1;
    701 
    702 					}
    703 
    704 				}
    705 
    706 			ss.Truncate (lower);
    707 
    708 			len = ss.Get_SystemEncoding (buffer);
    709 
    710 			}
    711 
    712 		stream.Put_uint16 ((uint16) len);
    713 
    714 		stream.Put (buffer.Buffer_char (), len);
    715 
    716 		}
    717 
    718 	}
    719 /*****************************************************************************/
    720 
    721 dng_memory_block * dng_iptc::Spool (dng_memory_allocator &allocator,
    722 									bool padForTIFF)
    723 	{
    724 
    725 	uint32 j;
    726 
    727 	char s [64];
    728 
    729 	dng_memory_stream stream (allocator, NULL, 2048);
    730 
    731 	stream.SetBigEndian ();
    732 
    733 	// Medata working group - now we just always write UTF-8.
    734 
    735 	CharSet charSet = kCharSetUTF8;
    736 
    737 	// UTF-8 encoding marker.
    738 
    739 	if (charSet == kCharSetUTF8)
    740 		{
    741 
    742 		stream.Put_uint16 (0x1C01);
    743 		stream.Put_uint8  (90);
    744 		stream.Put_uint16 (3);
    745 		stream.Put_uint8  (27);
    746 		stream.Put_uint8  (0x25);
    747 		stream.Put_uint8  (0x47);
    748 
    749 		}
    750 
    751 	stream.Put_uint16 (0x1C02);
    752 	stream.Put_uint8  (kRecordVersionSet);
    753 	stream.Put_uint16 (2);
    754 	stream.Put_uint16 (4);
    755 
    756 	SpoolString (stream,
    757 				 fTitle,
    758 				 kObjectNameSet,
    759 				 64,
    760 				 charSet);
    761 
    762 	if (fUrgency >= 0)
    763 		{
    764 
    765 		sprintf (s, "%1u", (unsigned) fUrgency);
    766 
    767 		stream.Put_uint16 (0x1C02);
    768 		stream.Put_uint8  (kUrgencySet);
    769 
    770 		stream.Put_uint16 (1);
    771 
    772 		stream.Put (s, 1);
    773 
    774 		}
    775 
    776 	SpoolString (stream,
    777 				 fCategory,
    778 				 kCategorySet,
    779 				 3,
    780 				 charSet);
    781 
    782 	for (j = 0; j < fSupplementalCategories.Count (); j++)
    783 		{
    784 
    785 		SpoolString (stream,
    786 				 	 fSupplementalCategories [j],
    787 				     kSupplementalCategoriesSet,
    788 				     32,
    789 					 charSet);
    790 
    791 		}
    792 
    793 	for (j = 0; j < fKeywords.Count (); j++)
    794 		{
    795 
    796 		SpoolString (stream,
    797 				 	 fKeywords [j],
    798 				     kKeywordsSet,
    799 				     64,
    800 					 charSet);
    801 
    802 		}
    803 
    804 	SpoolString (stream,
    805 				 fInstructions,
    806 				 kSpecialInstructionsSet,
    807 				 255,
    808 				 charSet);
    809 
    810 	if (fDateTimeCreated.IsValid ())
    811 		{
    812 
    813 		dng_string dateString = fDateTimeCreated.Encode_IPTC_Date ();
    814 
    815 		if (dateString.NotEmpty ())
    816 			{
    817 
    818 			DNG_ASSERT (dateString.Length () == 8, "Wrong length IPTC date");
    819 
    820 			stream.Put_uint16 (0x1C02);
    821 			stream.Put_uint8  (kDateCreatedSet);
    822 
    823 			stream.Put_uint16 (8);
    824 
    825 			stream.Put (dateString.Get (), 8);
    826 
    827 			}
    828 
    829 		dng_string timeString = fDateTimeCreated.Encode_IPTC_Time ();
    830 
    831 		if (timeString.NotEmpty ())
    832 			{
    833 
    834 			stream.Put_uint16 (0x1C02);
    835 			stream.Put_uint8  (kTimeCreatedSet);
    836 
    837 			stream.Put_uint16 ((uint16)timeString.Length ());
    838 
    839 			stream.Put (timeString.Get (), timeString.Length ());
    840 
    841 			}
    842 
    843 		}
    844 
    845 	if (fDigitalCreationDateTime.IsValid ())
    846 		{
    847 
    848 		dng_string dateString = fDigitalCreationDateTime.Encode_IPTC_Date ();
    849 
    850 		if (dateString.NotEmpty ())
    851 			{
    852 
    853 			DNG_ASSERT (dateString.Length () == 8, "Wrong length IPTC date");
    854 
    855 			stream.Put_uint16 (0x1C02);
    856 			stream.Put_uint8  (kDigitalCreationDateSet);
    857 
    858 			stream.Put_uint16 (8);
    859 
    860 			stream.Put (dateString.Get (), 8);
    861 
    862 			}
    863 
    864 		dng_string timeString = fDigitalCreationDateTime.Encode_IPTC_Time ();
    865 
    866 		if (timeString.NotEmpty ())
    867 			{
    868 
    869 			stream.Put_uint16 (0x1C02);
    870 			stream.Put_uint8  (kDigitalCreationTimeSet);
    871 
    872 			stream.Put_uint16 ((uint16)timeString.Length ());
    873 
    874 			stream.Put (timeString.Get (), timeString.Length ());
    875 
    876 			}
    877 
    878 		}
    879 
    880 	for (j = 0; j < fAuthors.Count (); j++)
    881 		{
    882 
    883 		SpoolString (stream,
    884 					 fAuthors [j],
    885 					 kBylineSet,
    886 					 32,
    887 					 charSet);
    888 
    889 		}
    890 
    891 	SpoolString (stream,
    892 				 fAuthorsPosition,
    893 				 kBylineTitleSet,
    894 				 32,
    895 				 charSet);
    896 
    897 	SpoolString (stream,
    898 				 fCity,
    899 				 kCitySet,
    900 				 32,
    901 				 charSet);
    902 
    903 	SpoolString (stream,
    904 				 fLocation,
    905 				 kSublocationSet,
    906 				 32,
    907 				 charSet);
    908 
    909 	SpoolString (stream,
    910 				 fState,
    911 				 kProvinceStateSet,
    912 				 32,
    913 				 charSet);
    914 
    915 	SpoolString (stream,
    916 				 fCountryCode,
    917 				 kCountryCodeSet,
    918 				 3,
    919 				 charSet);
    920 
    921 	SpoolString (stream,
    922 				 fCountry,
    923 				 kCountryNameSet,
    924 				 64,
    925 				 charSet);
    926 
    927 	SpoolString (stream,
    928 				 fTransmissionReference,
    929 				 kOriginalTransmissionReferenceSet,
    930 				 32,
    931 				 charSet);
    932 
    933 	SpoolString (stream,
    934 				 fHeadline,
    935 				 kHeadlineSet,
    936 				 255,
    937 				 charSet);
    938 
    939 	SpoolString (stream,
    940 				 fCredit,
    941 				 kCreditSet,
    942 				 32,
    943 				 charSet);
    944 
    945 	SpoolString (stream,
    946 				 fSource,
    947 				 kSourceSet,
    948 				 32,
    949 				 charSet);
    950 
    951 	SpoolString (stream,
    952 				 fCopyrightNotice,
    953 				 kCopyrightNoticeSet,
    954 				 128,
    955 				 charSet);
    956 
    957 	SpoolString (stream,
    958 				 fDescription,
    959 				 kCaptionSet,
    960 				 2000,
    961 				 charSet);
    962 
    963 	SpoolString (stream,
    964 				 fDescriptionWriter,
    965 				 kCaptionWriterSet,
    966 				 32,
    967 				 charSet);
    968 
    969 	if (padForTIFF)
    970 		{
    971 
    972 		while (stream.Length () & 3)
    973 			{
    974 			stream.Put_uint8 (0);
    975 			}
    976 
    977 		}
    978 
    979 	stream.Flush ();
    980 
    981 	return stream.AsMemoryBlock (allocator);
    982 
    983 	}
    984 
    985 /*****************************************************************************/
    986