You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6978 lines
139 KiB
6978 lines
139 KiB
/*****************************************************************************/
|
|
// Copyright 2006-2012 Adobe Systems Incorporated
|
|
// All Rights Reserved.
|
|
//
|
|
// NOTICE: Adobe permits you to use, modify, and distribute this file in
|
|
// accordance with the terms of the Adobe license agreement accompanying it.
|
|
/*****************************************************************************/
|
|
|
|
/* $Id: //mondo/dng_sdk_1_4/dng_sdk/source/dng_image_writer.cpp#4 $ */
|
|
/* $DateTime: 2012/06/14 20:24:41 $ */
|
|
/* $Change: 835078 $ */
|
|
/* $Author: tknoll $ */
|
|
|
|
/*****************************************************************************/
|
|
|
|
#include "dng_image_writer.h"
|
|
|
|
#include "dng_abort_sniffer.h"
|
|
#include "dng_area_task.h"
|
|
#include "dng_bottlenecks.h"
|
|
#include "dng_camera_profile.h"
|
|
#include "dng_color_space.h"
|
|
#include "dng_exif.h"
|
|
#include "dng_flags.h"
|
|
#include "dng_exceptions.h"
|
|
#include "dng_host.h"
|
|
#include "dng_ifd.h"
|
|
#include "dng_image.h"
|
|
#include "dng_jpeg_image.h"
|
|
#include "dng_lossless_jpeg.h"
|
|
#include "dng_memory.h"
|
|
#include "dng_memory_stream.h"
|
|
#include "dng_negative.h"
|
|
#include "dng_pixel_buffer.h"
|
|
#include "dng_preview.h"
|
|
#include "dng_read_image.h"
|
|
#include "dng_safe_arithmetic.h"
|
|
#include "dng_stream.h"
|
|
#include "dng_string_list.h"
|
|
#include "dng_tag_codes.h"
|
|
#include "dng_tag_values.h"
|
|
#include "dng_utils.h"
|
|
|
|
#if qDNGUseXMP
|
|
#include "dng_xmp.h"
|
|
#endif
|
|
|
|
#include "zlib.h"
|
|
|
|
#if qDNGUseLibJPEG
|
|
#include "dng_jpeglib.h"
|
|
#endif
|
|
|
|
/*****************************************************************************/
|
|
|
|
// Defines for testing DNG 1.2 features.
|
|
|
|
//#define qTestRowInterleave 2
|
|
|
|
//#define qTestSubTileBlockRows 2
|
|
//#define qTestSubTileBlockCols 2
|
|
|
|
/*****************************************************************************/
|
|
|
|
dng_resolution::dng_resolution ()
|
|
|
|
: fXResolution ()
|
|
, fYResolution ()
|
|
|
|
, fResolutionUnit (0)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
static void SpoolAdobeData (dng_stream &stream,
|
|
const dng_metadata *metadata,
|
|
const dng_jpeg_preview *preview,
|
|
const dng_memory_block *imageResources)
|
|
{
|
|
|
|
TempBigEndian tempEndian (stream);
|
|
|
|
#if qDNGUseXMP
|
|
|
|
if (metadata && metadata->GetXMP ())
|
|
{
|
|
|
|
bool marked = false;
|
|
|
|
if (metadata->GetXMP ()->GetBoolean (XMP_NS_XAP_RIGHTS,
|
|
"Marked",
|
|
marked))
|
|
{
|
|
|
|
stream.Put_uint32 (DNG_CHAR4 ('8','B','I','M'));
|
|
stream.Put_uint16 (1034);
|
|
stream.Put_uint16 (0);
|
|
|
|
stream.Put_uint32 (1);
|
|
|
|
stream.Put_uint8 (marked ? 1 : 0);
|
|
|
|
stream.Put_uint8 (0);
|
|
|
|
}
|
|
|
|
dng_string webStatement;
|
|
|
|
if (metadata->GetXMP ()->GetString (XMP_NS_XAP_RIGHTS,
|
|
"WebStatement",
|
|
webStatement))
|
|
{
|
|
|
|
dng_memory_data buffer;
|
|
|
|
uint32 size = webStatement.Get_SystemEncoding (buffer);
|
|
|
|
if (size > 0)
|
|
{
|
|
|
|
stream.Put_uint32 (DNG_CHAR4 ('8','B','I','M'));
|
|
stream.Put_uint16 (1035);
|
|
stream.Put_uint16 (0);
|
|
|
|
stream.Put_uint32 (size);
|
|
|
|
stream.Put (buffer.Buffer (), size);
|
|
|
|
if (size & 1)
|
|
stream.Put_uint8 (0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
if (preview)
|
|
{
|
|
|
|
preview->SpoolAdobeThumbnail (stream);
|
|
|
|
}
|
|
|
|
if (metadata && metadata->IPTCLength ())
|
|
{
|
|
|
|
dng_fingerprint iptcDigest = metadata->IPTCDigest ();
|
|
|
|
if (iptcDigest.IsValid ())
|
|
{
|
|
|
|
stream.Put_uint32 (DNG_CHAR4 ('8','B','I','M'));
|
|
stream.Put_uint16 (1061);
|
|
stream.Put_uint16 (0);
|
|
|
|
stream.Put_uint32 (16);
|
|
|
|
stream.Put (iptcDigest.data, 16);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (imageResources)
|
|
{
|
|
|
|
uint32 size = imageResources->LogicalSize ();
|
|
|
|
stream.Put (imageResources->Buffer (), size);
|
|
|
|
if (size & 1)
|
|
stream.Put_uint8 (0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
static dng_memory_block * BuildAdobeData (dng_host &host,
|
|
const dng_metadata *metadata,
|
|
const dng_jpeg_preview *preview,
|
|
const dng_memory_block *imageResources)
|
|
{
|
|
|
|
dng_memory_stream stream (host.Allocator ());
|
|
|
|
SpoolAdobeData (stream,
|
|
metadata,
|
|
preview,
|
|
imageResources);
|
|
|
|
return stream.AsMemoryBlock (host.Allocator ());
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
tag_string::tag_string (uint16 code,
|
|
const dng_string &s,
|
|
bool forceASCII)
|
|
|
|
: tiff_tag (code, ttAscii, 0)
|
|
|
|
, fString (s)
|
|
|
|
{
|
|
|
|
if (forceASCII)
|
|
{
|
|
|
|
// Metadata working group recommendation - go ahead
|
|
// write UTF-8 into ASCII tag strings, rather than
|
|
// actually force the strings to ASCII. There is a matching
|
|
// change on the reading side to assume UTF-8 if the string
|
|
// contains a valid UTF-8 string.
|
|
//
|
|
// fString.ForceASCII ();
|
|
|
|
}
|
|
|
|
else if (!fString.IsASCII ())
|
|
{
|
|
|
|
fType = ttByte;
|
|
|
|
}
|
|
|
|
fCount = fString.Length () + 1;
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void tag_string::Put (dng_stream &stream) const
|
|
{
|
|
|
|
stream.Put (fString.Get (), Size ());
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
tag_encoded_text::tag_encoded_text (uint16 code,
|
|
const dng_string &text)
|
|
|
|
: tiff_tag (code, ttUndefined, 0)
|
|
|
|
, fText (text)
|
|
|
|
, fUTF16 ()
|
|
|
|
{
|
|
|
|
if (fText.IsASCII ())
|
|
{
|
|
|
|
fCount = 8 + fText.Length ();
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
|
|
fCount = 8 + fText.Get_UTF16 (fUTF16) * 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void tag_encoded_text::Put (dng_stream &stream) const
|
|
{
|
|
|
|
if (fUTF16.Buffer ())
|
|
{
|
|
|
|
stream.Put ("UNICODE\000", 8);
|
|
|
|
uint32 chars = (fCount - 8) >> 1;
|
|
|
|
const uint16 *buf = fUTF16.Buffer_uint16 ();
|
|
|
|
for (uint32 j = 0; j < chars; j++)
|
|
{
|
|
|
|
stream.Put_uint16 (buf [j]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
|
|
stream.Put ("ASCII\000\000\000", 8);
|
|
|
|
stream.Put (fText.Get (), fCount - 8);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void tag_data_ptr::Put (dng_stream &stream) const
|
|
{
|
|
|
|
// If we are swapping bytes, we need to swap with the right size
|
|
// entries.
|
|
|
|
if (stream.SwapBytes ())
|
|
{
|
|
|
|
switch (Type ())
|
|
{
|
|
|
|
// Two byte entries.
|
|
|
|
case ttShort:
|
|
case ttSShort:
|
|
case ttUnicode:
|
|
{
|
|
|
|
const uint16 *p = (const uint16 *) fData;
|
|
|
|
uint32 entries = (Size () >> 1);
|
|
|
|
for (uint32 j = 0; j < entries; j++)
|
|
{
|
|
|
|
stream.Put_uint16 (p [j]);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Four byte entries.
|
|
|
|
case ttLong:
|
|
case ttSLong:
|
|
case ttRational:
|
|
case ttSRational:
|
|
case ttIFD:
|
|
case ttFloat:
|
|
case ttComplex:
|
|
{
|
|
|
|
const uint32 *p = (const uint32 *) fData;
|
|
|
|
uint32 entries = (Size () >> 2);
|
|
|
|
for (uint32 j = 0; j < entries; j++)
|
|
{
|
|
|
|
stream.Put_uint32 (p [j]);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Eight byte entries.
|
|
|
|
case ttDouble:
|
|
{
|
|
|
|
const real64 *p = (const real64 *) fData;
|
|
|
|
uint32 entries = (Size () >> 3);
|
|
|
|
for (uint32 j = 0; j < entries; j++)
|
|
{
|
|
|
|
stream.Put_real64 (p [j]);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Entries don't need to be byte swapped. Fall through
|
|
// to non-byte swapped case.
|
|
|
|
default:
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Non-byte swapped case.
|
|
|
|
stream.Put (fData, Size ());
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
tag_matrix::tag_matrix (uint16 code,
|
|
const dng_matrix &m)
|
|
|
|
: tag_srational_ptr (code, fEntry, m.Rows () * m.Cols ())
|
|
|
|
{
|
|
|
|
uint32 index = 0;
|
|
|
|
for (uint32 r = 0; r < m.Rows (); r++)
|
|
for (uint32 c = 0; c < m.Cols (); c++)
|
|
{
|
|
|
|
fEntry [index].Set_real64 (m [r] [c], 10000);
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
tag_icc_profile::tag_icc_profile (const void *profileData,
|
|
uint32 profileSize)
|
|
|
|
: tag_data_ptr (tcICCProfile,
|
|
ttUndefined,
|
|
0,
|
|
NULL)
|
|
|
|
{
|
|
|
|
if (profileData && profileSize)
|
|
{
|
|
|
|
SetCount (profileSize);
|
|
SetData (profileData);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
void tag_cfa_pattern::Put (dng_stream &stream) const
|
|
{
|
|
|
|
stream.Put_uint16 ((uint16) fCols);
|
|
stream.Put_uint16 ((uint16) fRows);
|
|
|
|
for (uint32 col = 0; col < fCols; col++)
|
|
for (uint32 row = 0; row < fRows; row++)
|
|
{
|
|
|
|
stream.Put_uint8 (fPattern [row * kMaxCFAPattern + col]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
tag_exif_date_time::tag_exif_date_time (uint16 code,
|
|
const dng_date_time &dt)
|
|
|
|
: tag_data_ptr (code, ttAscii, 20, fData)
|
|
|
|
{
|
|
|
|
if (dt.IsValid ())
|
|
{
|
|
|
|
sprintf (fData,
|
|
"%04d:%02d:%02d %02d:%02d:%02d",
|
|
(int) dt.fYear,
|
|
(int) dt.fMonth,
|
|
(int) dt.fDay,
|
|
(int) dt.fHour,
|
|
(int) dt.fMinute,
|
|
(int) dt.fSecond);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
tag_iptc::tag_iptc (const void *data,
|
|
uint32 length)
|
|
|
|
: tiff_tag (tcIPTC_NAA, ttLong, (length + 3) >> 2)
|
|
|
|
, fData (data )
|
|
, fLength (length)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
void tag_iptc::Put (dng_stream &stream) const
|
|
{
|
|
|
|
// Note: For historical compatiblity reasons, the standard TIFF data
|
|
// type for IPTC data is ttLong, but without byte swapping. This really
|
|
// should be ttUndefined, but doing the right thing would break some
|
|
// existing readers.
|
|
|
|
stream.Put (fData, fLength);
|
|
|
|
// Pad with zeros to get to long word boundary.
|
|
|
|
uint32 extra = fCount * 4 - fLength;
|
|
|
|
while (extra--)
|
|
{
|
|
stream.Put_uint8 (0);
|
|
}
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
tag_xmp::tag_xmp (const dng_xmp *xmp)
|
|
|
|
: tag_uint8_ptr (tcXMP, NULL, 0)
|
|
|
|
, fBuffer ()
|
|
|
|
{
|
|
|
|
#if qDNGUseXMP
|
|
|
|
if (xmp)
|
|
{
|
|
|
|
fBuffer.Reset (xmp->Serialize (true));
|
|
|
|
if (fBuffer.Get ())
|
|
{
|
|
|
|
SetData (fBuffer->Buffer_uint8 ());
|
|
|
|
SetCount (fBuffer->LogicalSize ());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
void dng_tiff_directory::Add (const tiff_tag *tag)
|
|
{
|
|
|
|
if (fEntries >= kMaxEntries)
|
|
{
|
|
ThrowProgramError ();
|
|
}
|
|
|
|
// Tags must be sorted in increasing order of tag code.
|
|
|
|
uint32 index = fEntries;
|
|
|
|
for (uint32 j = 0; j < fEntries; j++)
|
|
{
|
|
|
|
if (tag->Code () < fTag [j]->Code ())
|
|
{
|
|
index = j;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
for (uint32 k = fEntries; k > index; k--)
|
|
{
|
|
|
|
fTag [k] = fTag [k - 1];
|
|
|
|
}
|
|
|
|
fTag [index] = tag;
|
|
|
|
fEntries++;
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
uint32 dng_tiff_directory::Size () const
|
|
{
|
|
|
|
if (!fEntries) return 0;
|
|
|
|
uint32 size = fEntries * 12 + 6;
|
|
|
|
for (uint32 index = 0; index < fEntries; index++)
|
|
{
|
|
|
|
uint32 tagSize = fTag [index]->Size ();
|
|
|
|
if (tagSize > 4)
|
|
{
|
|
|
|
size += (tagSize + 1) & ~1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
void dng_tiff_directory::Put (dng_stream &stream,
|
|
OffsetsBase offsetsBase,
|
|
uint32 explicitBase) const
|
|
{
|
|
|
|
if (!fEntries) return;
|
|
|
|
uint32 index;
|
|
|
|
uint32 bigData = fEntries * 12 + 6;
|
|
|
|
if (offsetsBase == offsetsRelativeToStream)
|
|
bigData += (uint32) stream.Position ();
|
|
|
|
else if (offsetsBase == offsetsRelativeToExplicitBase)
|
|
bigData += explicitBase;
|
|
|
|
stream.Put_uint16 ((uint16) fEntries);
|
|
|
|
for (index = 0; index < fEntries; index++)
|
|
{
|
|
|
|
const tiff_tag &tag = *fTag [index];
|
|
|
|
stream.Put_uint16 (tag.Code ());
|
|
stream.Put_uint16 (tag.Type ());
|
|
stream.Put_uint32 (tag.Count ());
|
|
|
|
uint32 size = tag.Size ();
|
|
|
|
if (size <= 4)
|
|
{
|
|
|
|
tag.Put (stream);
|
|
|
|
while (size < 4)
|
|
{
|
|
stream.Put_uint8 (0);
|
|
size++;
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
|
|
stream.Put_uint32 (bigData);
|
|
|
|
bigData += (size + 1) & ~1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
stream.Put_uint32 (fChained); // Next IFD offset
|
|
|
|
for (index = 0; index < fEntries; index++)
|
|
{
|
|
|
|
const tiff_tag &tag = *fTag [index];
|
|
|
|
uint32 size = tag.Size ();
|
|
|
|
if (size > 4)
|
|
{
|
|
|
|
tag.Put (stream);
|
|
|
|
if (size & 1)
|
|
stream.Put_uint8 (0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
dng_basic_tag_set::dng_basic_tag_set (dng_tiff_directory &directory,
|
|
const dng_ifd &info)
|
|
|
|
: fNewSubFileType (tcNewSubFileType, info.fNewSubFileType)
|
|
|
|
, fImageWidth (tcImageWidth , info.fImageWidth )
|
|
, fImageLength (tcImageLength, info.fImageLength)
|
|
|
|
, fPhotoInterpretation (tcPhotometricInterpretation,
|
|
(uint16) info.fPhotometricInterpretation)
|
|
|
|
, fFillOrder (tcFillOrder, 1)
|
|
|
|
, fSamplesPerPixel (tcSamplesPerPixel, (uint16) info.fSamplesPerPixel)
|
|
|
|
, fBitsPerSample (tcBitsPerSample,
|
|
fBitsPerSampleData,
|
|
info.fSamplesPerPixel)
|
|
|
|
, fStrips (info.fUsesStrips)
|
|
|
|
, fTileWidth (tcTileWidth, info.fTileWidth)
|
|
|
|
, fTileLength (fStrips ? tcRowsPerStrip : tcTileLength,
|
|
info.fTileLength)
|
|
|
|
, fTileInfoBuffer (info.TilesPerImage (), 8)
|
|
|
|
, fTileOffsetData (fTileInfoBuffer.Buffer_uint32 ())
|
|
|
|
, fTileOffsets (fStrips ? tcStripOffsets : tcTileOffsets,
|
|
fTileOffsetData,
|
|
info.TilesPerImage ())
|
|
|
|
, fTileByteCountData (fTileOffsetData + info.TilesPerImage ())
|
|
|
|
, fTileByteCounts (fStrips ? tcStripByteCounts : tcTileByteCounts,
|
|
fTileByteCountData,
|
|
info.TilesPerImage ())
|
|
|
|
, fPlanarConfiguration (tcPlanarConfiguration, pcInterleaved)
|
|
|
|
, fCompression (tcCompression, (uint16) info.fCompression)
|
|
, fPredictor (tcPredictor , (uint16) info.fPredictor )
|
|
|
|
, fExtraSamples (tcExtraSamples,
|
|
fExtraSamplesData,
|
|
info.fExtraSamplesCount)
|
|
|
|
, fSampleFormat (tcSampleFormat,
|
|
fSampleFormatData,
|
|
info.fSamplesPerPixel)
|
|
|
|
, fRowInterleaveFactor (tcRowInterleaveFactor,
|
|
(uint16) info.fRowInterleaveFactor)
|
|
|
|
, fSubTileBlockSize (tcSubTileBlockSize,
|
|
fSubTileBlockSizeData,
|
|
2)
|
|
|
|
{
|
|
|
|
uint32 j;
|
|
|
|
for (j = 0; j < info.fSamplesPerPixel; j++)
|
|
{
|
|
|
|
fBitsPerSampleData [j] = (uint16) info.fBitsPerSample [0];
|
|
|
|
}
|
|
|
|
directory.Add (&fNewSubFileType);
|
|
|
|
directory.Add (&fImageWidth);
|
|
directory.Add (&fImageLength);
|
|
|
|
directory.Add (&fPhotoInterpretation);
|
|
|
|
directory.Add (&fSamplesPerPixel);
|
|
|
|
directory.Add (&fBitsPerSample);
|
|
|
|
if (info.fBitsPerSample [0] != 8 &&
|
|
info.fBitsPerSample [0] != 16 &&
|
|
info.fBitsPerSample [0] != 32)
|
|
{
|
|
|
|
directory.Add (&fFillOrder);
|
|
|
|
}
|
|
|
|
if (!fStrips)
|
|
{
|
|
|
|
directory.Add (&fTileWidth);
|
|
|
|
}
|
|
|
|
directory.Add (&fTileLength);
|
|
|
|
directory.Add (&fTileOffsets);
|
|
directory.Add (&fTileByteCounts);
|
|
|
|
directory.Add (&fPlanarConfiguration);
|
|
|
|
directory.Add (&fCompression);
|
|
|
|
if (info.fPredictor != cpNullPredictor)
|
|
{
|
|
|
|
directory.Add (&fPredictor);
|
|
|
|
}
|
|
|
|
if (info.fExtraSamplesCount != 0)
|
|
{
|
|
|
|
for (j = 0; j < info.fExtraSamplesCount; j++)
|
|
{
|
|
fExtraSamplesData [j] = (uint16) info.fExtraSamples [j];
|
|
}
|
|
|
|
directory.Add (&fExtraSamples);
|
|
|
|
}
|
|
|
|
if (info.fSampleFormat [0] != sfUnsignedInteger)
|
|
{
|
|
|
|
for (j = 0; j < info.fSamplesPerPixel; j++)
|
|
{
|
|
fSampleFormatData [j] = (uint16) info.fSampleFormat [j];
|
|
}
|
|
|
|
directory.Add (&fSampleFormat);
|
|
|
|
}
|
|
|
|
if (info.fRowInterleaveFactor != 1)
|
|
{
|
|
|
|
directory.Add (&fRowInterleaveFactor);
|
|
|
|
}
|
|
|
|
if (info.fSubTileBlockRows != 1 ||
|
|
info.fSubTileBlockCols != 1)
|
|
{
|
|
|
|
fSubTileBlockSizeData [0] = (uint16) info.fSubTileBlockRows;
|
|
fSubTileBlockSizeData [1] = (uint16) info.fSubTileBlockCols;
|
|
|
|
directory.Add (&fSubTileBlockSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
exif_tag_set::exif_tag_set (dng_tiff_directory &directory,
|
|
const dng_exif &exif,
|
|
bool makerNoteSafe,
|
|
const void *makerNoteData,
|
|
uint32 makerNoteLength,
|
|
bool insideDNG)
|
|
|
|
: fExifIFD ()
|
|
, fGPSIFD ()
|
|
|
|
, fExifLink (tcExifIFD, 0)
|
|
, fGPSLink (tcGPSInfo, 0)
|
|
|
|
, fAddedExifLink (false)
|
|
, fAddedGPSLink (false)
|
|
|
|
, fExifVersion (tcExifVersion, ttUndefined, 4, fExifVersionData)
|
|
|
|
, fExposureTime (tcExposureTime , exif.fExposureTime )
|
|
, fShutterSpeedValue (tcShutterSpeedValue, exif.fShutterSpeedValue)
|
|
|
|
, fFNumber (tcFNumber , exif.fFNumber )
|
|
, fApertureValue (tcApertureValue, exif.fApertureValue)
|
|
|
|
, fBrightnessValue (tcBrightnessValue, exif.fBrightnessValue)
|
|
|
|
, fExposureBiasValue (tcExposureBiasValue, exif.fExposureBiasValue)
|
|
|
|
, fMaxApertureValue (tcMaxApertureValue , exif.fMaxApertureValue)
|
|
|
|
, fSubjectDistance (tcSubjectDistance, exif.fSubjectDistance)
|
|
|
|
, fFocalLength (tcFocalLength, exif.fFocalLength)
|
|
|
|
// Special case: the EXIF 2.2 standard represents ISO speed ratings with 2 bytes,
|
|
// which cannot hold ISO speed ratings above 65535 (e.g., 102400). In these
|
|
// cases, we write the maximum representable ISO speed rating value in the EXIF
|
|
// tag, i.e., 65535.
|
|
|
|
, fISOSpeedRatings (tcISOSpeedRatings,
|
|
(uint16) Min_uint32 (65535,
|
|
exif.fISOSpeedRatings [0]))
|
|
|
|
, fSensitivityType (tcSensitivityType, (uint16) exif.fSensitivityType)
|
|
|
|
, fStandardOutputSensitivity (tcStandardOutputSensitivity, exif.fStandardOutputSensitivity)
|
|
|
|
, fRecommendedExposureIndex (tcRecommendedExposureIndex, exif.fRecommendedExposureIndex)
|
|
|
|
, fISOSpeed (tcISOSpeed, exif.fISOSpeed)
|
|
|
|
, fISOSpeedLatitudeyyy (tcISOSpeedLatitudeyyy, exif.fISOSpeedLatitudeyyy)
|
|
|
|
, fISOSpeedLatitudezzz (tcISOSpeedLatitudezzz, exif.fISOSpeedLatitudezzz)
|
|
|
|
, fFlash (tcFlash, (uint16) exif.fFlash)
|
|
|
|
, fExposureProgram (tcExposureProgram, (uint16) exif.fExposureProgram)
|
|
|
|
, fMeteringMode (tcMeteringMode, (uint16) exif.fMeteringMode)
|
|
|
|
, fLightSource (tcLightSource, (uint16) exif.fLightSource)
|
|
|
|
, fSensingMethod (tcSensingMethodExif, (uint16) exif.fSensingMethod)
|
|
|
|
, fFocalLength35mm (tcFocalLengthIn35mmFilm, (uint16) exif.fFocalLengthIn35mmFilm)
|
|
|
|
, fFileSourceData ((uint8) exif.fFileSource)
|
|
, fFileSource (tcFileSource, ttUndefined, 1, &fFileSourceData)
|
|
|
|
, fSceneTypeData ((uint8) exif.fSceneType)
|
|
, fSceneType (tcSceneType, ttUndefined, 1, &fSceneTypeData)
|
|
|
|
, fCFAPattern (tcCFAPatternExif,
|
|
exif.fCFARepeatPatternRows,
|
|
exif.fCFARepeatPatternCols,
|
|
&exif.fCFAPattern [0] [0])
|
|
|
|
, fCustomRendered (tcCustomRendered , (uint16) exif.fCustomRendered )
|
|
, fExposureMode (tcExposureMode , (uint16) exif.fExposureMode )
|
|
, fWhiteBalance (tcWhiteBalance , (uint16) exif.fWhiteBalance )
|
|
, fSceneCaptureType (tcSceneCaptureType , (uint16) exif.fSceneCaptureType )
|
|
, fGainControl (tcGainControl , (uint16) exif.fGainControl )
|
|
, fContrast (tcContrast , (uint16) exif.fContrast )
|
|
, fSaturation (tcSaturation , (uint16) exif.fSaturation )
|
|
, fSharpness (tcSharpness , (uint16) exif.fSharpness )
|
|
, fSubjectDistanceRange (tcSubjectDistanceRange, (uint16) exif.fSubjectDistanceRange)
|
|
|
|
, fDigitalZoomRatio (tcDigitalZoomRatio, exif.fDigitalZoomRatio)
|
|
|
|
, fExposureIndex (tcExposureIndexExif, exif.fExposureIndex)
|
|
|
|
, fImageNumber (tcImageNumber, exif.fImageNumber)
|
|
|
|
, fSelfTimerMode (tcSelfTimerMode, (uint16) exif.fSelfTimerMode)
|
|
|
|
, fBatteryLevelA (tcBatteryLevel, exif.fBatteryLevelA)
|
|
, fBatteryLevelR (tcBatteryLevel, exif.fBatteryLevelR)
|
|
|
|
, fFocalPlaneXResolution (tcFocalPlaneXResolutionExif, exif.fFocalPlaneXResolution)
|
|
, fFocalPlaneYResolution (tcFocalPlaneYResolutionExif, exif.fFocalPlaneYResolution)
|
|
|
|
, fFocalPlaneResolutionUnit (tcFocalPlaneResolutionUnitExif, (uint16) exif.fFocalPlaneResolutionUnit)
|
|
|
|
, fSubjectArea (tcSubjectArea, fSubjectAreaData, exif.fSubjectAreaCount)
|
|
|
|
, fLensInfo (tcLensInfo, fLensInfoData, 4)
|
|
|
|
, fDateTime (tcDateTime , exif.fDateTime .DateTime ())
|
|
, fDateTimeOriginal (tcDateTimeOriginal , exif.fDateTimeOriginal .DateTime ())
|
|
, fDateTimeDigitized (tcDateTimeDigitized, exif.fDateTimeDigitized.DateTime ())
|
|
|
|
, fSubsecTime (tcSubsecTime, exif.fDateTime .Subseconds ())
|
|
, fSubsecTimeOriginal (tcSubsecTimeOriginal, exif.fDateTimeOriginal .Subseconds ())
|
|
, fSubsecTimeDigitized (tcSubsecTimeDigitized, exif.fDateTimeDigitized.Subseconds ())
|
|
|
|
, fMake (tcMake, exif.fMake)
|
|
|
|
, fModel (tcModel, exif.fModel)
|
|
|
|
, fArtist (tcArtist, exif.fArtist)
|
|
|
|
, fSoftware (tcSoftware, exif.fSoftware)
|
|
|
|
, fCopyright (tcCopyright, exif.fCopyright)
|
|
|
|
, fMakerNoteSafety (tcMakerNoteSafety, makerNoteSafe ? 1 : 0)
|
|
|
|
, fMakerNote (tcMakerNote, ttUndefined, makerNoteLength, makerNoteData)
|
|
|
|
, fImageDescription (tcImageDescription, exif.fImageDescription)
|
|
|
|
, fSerialNumber (tcCameraSerialNumber, exif.fCameraSerialNumber)
|
|
|
|
, fUserComment (tcUserComment, exif.fUserComment)
|
|
|
|
, fImageUniqueID (tcImageUniqueID, ttAscii, 33, fImageUniqueIDData)
|
|
|
|
// EXIF 2.3 tags.
|
|
|
|
, fCameraOwnerName (tcCameraOwnerNameExif, exif.fOwnerName )
|
|
, fBodySerialNumber (tcCameraSerialNumberExif, exif.fCameraSerialNumber)
|
|
, fLensSpecification (tcLensSpecificationExif, fLensInfoData, 4 )
|
|
, fLensMake (tcLensMakeExif, exif.fLensMake )
|
|
, fLensModel (tcLensModelExif, exif.fLensName )
|
|
, fLensSerialNumber (tcLensSerialNumberExif, exif.fLensSerialNumber )
|
|
|
|
, fGPSVersionID (tcGPSVersionID, fGPSVersionData, 4)
|
|
|
|
, fGPSLatitudeRef (tcGPSLatitudeRef, exif.fGPSLatitudeRef)
|
|
, fGPSLatitude (tcGPSLatitude, exif.fGPSLatitude, 3)
|
|
|
|
, fGPSLongitudeRef (tcGPSLongitudeRef, exif.fGPSLongitudeRef)
|
|
, fGPSLongitude (tcGPSLongitude, exif.fGPSLongitude, 3)
|
|
|
|
, fGPSAltitudeRef (tcGPSAltitudeRef, (uint8) exif.fGPSAltitudeRef)
|
|
, fGPSAltitude (tcGPSAltitude, exif.fGPSAltitude )
|
|
|
|
, fGPSTimeStamp (tcGPSTimeStamp, exif.fGPSTimeStamp, 3)
|
|
|
|
, fGPSSatellites (tcGPSSatellites , exif.fGPSSatellites )
|
|
, fGPSStatus (tcGPSStatus , exif.fGPSStatus )
|
|
, fGPSMeasureMode (tcGPSMeasureMode, exif.fGPSMeasureMode)
|
|
|
|
, fGPSDOP (tcGPSDOP, exif.fGPSDOP)
|
|
|
|
, fGPSSpeedRef (tcGPSSpeedRef, exif.fGPSSpeedRef)
|
|
, fGPSSpeed (tcGPSSpeed , exif.fGPSSpeed )
|
|
|
|
, fGPSTrackRef (tcGPSTrackRef, exif.fGPSTrackRef)
|
|
, fGPSTrack (tcGPSTrack , exif.fGPSTrack )
|
|
|
|
, fGPSImgDirectionRef (tcGPSImgDirectionRef, exif.fGPSImgDirectionRef)
|
|
, fGPSImgDirection (tcGPSImgDirection , exif.fGPSImgDirection )
|
|
|
|
, fGPSMapDatum (tcGPSMapDatum, exif.fGPSMapDatum)
|
|
|
|
, fGPSDestLatitudeRef (tcGPSDestLatitudeRef, exif.fGPSDestLatitudeRef)
|
|
, fGPSDestLatitude (tcGPSDestLatitude, exif.fGPSDestLatitude, 3)
|
|
|
|
, fGPSDestLongitudeRef (tcGPSDestLongitudeRef, exif.fGPSDestLongitudeRef)
|
|
, fGPSDestLongitude (tcGPSDestLongitude, exif.fGPSDestLongitude, 3)
|
|
|
|
, fGPSDestBearingRef (tcGPSDestBearingRef, exif.fGPSDestBearingRef)
|
|
, fGPSDestBearing (tcGPSDestBearing , exif.fGPSDestBearing )
|
|
|
|
, fGPSDestDistanceRef (tcGPSDestDistanceRef, exif.fGPSDestDistanceRef)
|
|
, fGPSDestDistance (tcGPSDestDistance , exif.fGPSDestDistance )
|
|
|
|
, fGPSProcessingMethod (tcGPSProcessingMethod, exif.fGPSProcessingMethod)
|
|
, fGPSAreaInformation (tcGPSAreaInformation , exif.fGPSAreaInformation )
|
|
|
|
, fGPSDateStamp (tcGPSDateStamp, exif.fGPSDateStamp)
|
|
|
|
, fGPSDifferential (tcGPSDifferential, (uint16) exif.fGPSDifferential)
|
|
|
|
, fGPSHPositioningError (tcGPSHPositioningError, exif.fGPSHPositioningError)
|
|
|
|
{
|
|
|
|
if (exif.fExifVersion)
|
|
{
|
|
|
|
fExifVersionData [0] = (uint8) (exif.fExifVersion >> 24);
|
|
fExifVersionData [1] = (uint8) (exif.fExifVersion >> 16);
|
|
fExifVersionData [2] = (uint8) (exif.fExifVersion >> 8);
|
|
fExifVersionData [3] = (uint8) (exif.fExifVersion );
|
|
|
|
fExifIFD.Add (&fExifVersion);
|
|
|
|
}
|
|
|
|
if (exif.fExposureTime.IsValid ())
|
|
{
|
|
fExifIFD.Add (&fExposureTime);
|
|
}
|
|
|
|
if (exif.fShutterSpeedValue.IsValid ())
|
|
{
|
|
fExifIFD.Add (&fShutterSpeedValue);
|
|
}
|
|
|
|
if (exif.fFNumber.IsValid ())
|
|
{
|
|
fExifIFD.Add (&fFNumber);
|
|
}
|
|
|
|
if (exif.fApertureValue.IsValid ())
|
|
{
|
|
fExifIFD.Add (&fApertureValue);
|
|
}
|
|
|
|
if (exif.fBrightnessValue.IsValid ())
|
|
{
|
|
fExifIFD.Add (&fBrightnessValue);
|
|
}
|
|
|
|
if (exif.fExposureBiasValue.IsValid ())
|
|
{
|
|
fExifIFD.Add (&fExposureBiasValue);
|
|
}
|
|
|
|
if (exif.fMaxApertureValue.IsValid ())
|
|
{
|
|
fExifIFD.Add (&fMaxApertureValue);
|
|
}
|
|
|
|
if (exif.fSubjectDistance.IsValid ())
|
|
{
|
|
fExifIFD.Add (&fSubjectDistance);
|
|
}
|
|
|
|
if (exif.fFocalLength.IsValid ())
|
|
{
|
|
fExifIFD.Add (&fFocalLength);
|
|
}
|
|
|
|
if (exif.fISOSpeedRatings [0] != 0)
|
|
{
|
|
fExifIFD.Add (&fISOSpeedRatings);
|
|
}
|
|
|
|
if (exif.fFlash <= 0x0FFFF)
|
|
{
|
|
fExifIFD.Add (&fFlash);
|
|
}
|
|
|
|
if (exif.fExposureProgram <= 0x0FFFF)
|
|
{
|
|
fExifIFD.Add (&fExposureProgram);
|
|
}
|
|
|
|
if (exif.fMeteringMode <= 0x0FFFF)
|
|
{
|
|
fExifIFD.Add (&fMeteringMode);
|
|
}
|
|
|
|
if (exif.fLightSource <= 0x0FFFF)
|
|
{
|
|
fExifIFD.Add (&fLightSource);
|
|
}
|
|
|
|
if (exif.fSensingMethod <= 0x0FFFF)
|
|
{
|
|
fExifIFD.Add (&fSensingMethod);
|
|
}
|
|
|
|
if (exif.fFocalLengthIn35mmFilm != 0)
|
|
{
|
|
fExifIFD.Add (&fFocalLength35mm);
|
|
}
|
|
|
|
if (exif.fFileSource <= 0x0FF)
|
|
{
|
|
fExifIFD.Add (&fFileSource);
|
|
}
|
|
|
|
if (exif.fSceneType <= 0x0FF)
|
|
{
|
|
fExifIFD.Add (&fSceneType);
|
|
}
|
|
|
|
if (exif.fCFARepeatPatternRows &&
|
|
exif.fCFARepeatPatternCols)
|
|
{
|
|
fExifIFD.Add (&fCFAPattern);
|
|
}
|
|
|
|
if (exif.fCustomRendered <= 0x0FFFF)
|
|
{
|
|
fExifIFD.Add (&fCustomRendered);
|
|
}
|
|
|
|
if (exif.fExposureMode <= 0x0FFFF)
|
|
{
|
|
fExifIFD.Add (&fExposureMode);
|
|
}
|
|
|
|
if (exif.fWhiteBalance <= 0x0FFFF)
|
|
{
|
|
fExifIFD.Add (&fWhiteBalance);
|
|
}
|
|
|
|
if (exif.fSceneCaptureType <= 0x0FFFF)
|
|
{
|
|
fExifIFD.Add (&fSceneCaptureType);
|
|
}
|
|
|
|
if (exif.fGainControl <= 0x0FFFF)
|
|
{
|
|
fExifIFD.Add (&fGainControl);
|
|
}
|
|
|
|
if (exif.fContrast <= 0x0FFFF)
|
|
{
|
|
fExifIFD.Add (&fContrast);
|
|
}
|
|
|
|
if (exif.fSaturation <= 0x0FFFF)
|
|
{
|
|
fExifIFD.Add (&fSaturation);
|
|
}
|
|
|
|
if (exif.fSharpness <= 0x0FFFF)
|
|
{
|
|
fExifIFD.Add (&fSharpness);
|
|
}
|
|
|
|
if (exif.fSubjectDistanceRange <= 0x0FFFF)
|
|
{
|
|
fExifIFD.Add (&fSubjectDistanceRange);
|
|
}
|
|
|
|
if (exif.fDigitalZoomRatio.IsValid ())
|
|
{
|
|
fExifIFD.Add (&fDigitalZoomRatio);
|
|
}
|
|
|
|
if (exif.fExposureIndex.IsValid ())
|
|
{
|
|
fExifIFD.Add (&fExposureIndex);
|
|
}
|
|
|
|
if (insideDNG) // TIFF-EP only tags
|
|
{
|
|
|
|
if (exif.fImageNumber != 0xFFFFFFFF)
|
|
{
|
|
directory.Add (&fImageNumber);
|
|
}
|
|
|
|
if (exif.fSelfTimerMode <= 0x0FFFF)
|
|
{
|
|
directory.Add (&fSelfTimerMode);
|
|
}
|
|
|
|
if (exif.fBatteryLevelA.NotEmpty ())
|
|
{
|
|
directory.Add (&fBatteryLevelA);
|
|
}
|
|
|
|
else if (exif.fBatteryLevelR.IsValid ())
|
|
{
|
|
directory.Add (&fBatteryLevelR);
|
|
}
|
|
|
|
}
|
|
|
|
if (exif.fFocalPlaneXResolution.IsValid ())
|
|
{
|
|
fExifIFD.Add (&fFocalPlaneXResolution);
|
|
}
|
|
|
|
if (exif.fFocalPlaneYResolution.IsValid ())
|
|
{
|
|
fExifIFD.Add (&fFocalPlaneYResolution);
|
|
}
|
|
|
|
if (exif.fFocalPlaneResolutionUnit <= 0x0FFFF)
|
|
{
|
|
fExifIFD.Add (&fFocalPlaneResolutionUnit);
|
|
}
|
|
|
|
if (exif.fSubjectAreaCount)
|
|
{
|
|
|
|
fSubjectAreaData [0] = (uint16) exif.fSubjectArea [0];
|
|
fSubjectAreaData [1] = (uint16) exif.fSubjectArea [1];
|
|
fSubjectAreaData [2] = (uint16) exif.fSubjectArea [2];
|
|
fSubjectAreaData [3] = (uint16) exif.fSubjectArea [3];
|
|
|
|
fExifIFD.Add (&fSubjectArea);
|
|
|
|
}
|
|
|
|
if (exif.fLensInfo [0].IsValid () &&
|
|
exif.fLensInfo [1].IsValid ())
|
|
{
|
|
|
|
fLensInfoData [0] = exif.fLensInfo [0];
|
|
fLensInfoData [1] = exif.fLensInfo [1];
|
|
fLensInfoData [2] = exif.fLensInfo [2];
|
|
fLensInfoData [3] = exif.fLensInfo [3];
|
|
|
|
if (insideDNG)
|
|
{
|
|
directory.Add (&fLensInfo);
|
|
}
|
|
|
|
}
|
|
|
|
if (exif.fDateTime.IsValid ())
|
|
{
|
|
|
|
directory.Add (&fDateTime);
|
|
|
|
if (exif.fDateTime.Subseconds ().NotEmpty ())
|
|
{
|
|
fExifIFD.Add (&fSubsecTime);
|
|
}
|
|
|
|
}
|
|
|
|
if (exif.fDateTimeOriginal.IsValid ())
|
|
{
|
|
|
|
fExifIFD.Add (&fDateTimeOriginal);
|
|
|
|
if (exif.fDateTimeOriginal.Subseconds ().NotEmpty ())
|
|
{
|
|
fExifIFD.Add (&fSubsecTimeOriginal);
|
|
}
|
|
|
|
}
|
|
|
|
if (exif.fDateTimeDigitized.IsValid ())
|
|
{
|
|
|
|
fExifIFD.Add (&fDateTimeDigitized);
|
|
|
|
if (exif.fDateTimeDigitized.Subseconds ().NotEmpty ())
|
|
{
|
|
fExifIFD.Add (&fSubsecTimeDigitized);
|
|
}
|
|
|
|
}
|
|
|
|
if (exif.fMake.NotEmpty ())
|
|
{
|
|
directory.Add (&fMake);
|
|
}
|
|
|
|
if (exif.fModel.NotEmpty ())
|
|
{
|
|
directory.Add (&fModel);
|
|
}
|
|
|
|
if (exif.fArtist.NotEmpty ())
|
|
{
|
|
directory.Add (&fArtist);
|
|
}
|
|
|
|
if (exif.fSoftware.NotEmpty ())
|
|
{
|
|
directory.Add (&fSoftware);
|
|
}
|
|
|
|
if (exif.fCopyright.NotEmpty ())
|
|
{
|
|
directory.Add (&fCopyright);
|
|
}
|
|
|
|
if (exif.fImageDescription.NotEmpty ())
|
|
{
|
|
directory.Add (&fImageDescription);
|
|
}
|
|
|
|
if (exif.fCameraSerialNumber.NotEmpty () && insideDNG)
|
|
{
|
|
directory.Add (&fSerialNumber);
|
|
}
|
|
|
|
if (makerNoteSafe && makerNoteData)
|
|
{
|
|
|
|
directory.Add (&fMakerNoteSafety);
|
|
|
|
fExifIFD.Add (&fMakerNote);
|
|
|
|
}
|
|
|
|
if (exif.fUserComment.NotEmpty ())
|
|
{
|
|
fExifIFD.Add (&fUserComment);
|
|
}
|
|
|
|
if (exif.fImageUniqueID.IsValid ())
|
|
{
|
|
|
|
for (uint32 j = 0; j < 16; j++)
|
|
{
|
|
|
|
sprintf (fImageUniqueIDData + j * 2,
|
|
"%02X",
|
|
(unsigned) exif.fImageUniqueID.data [j]);
|
|
|
|
}
|
|
|
|
fExifIFD.Add (&fImageUniqueID);
|
|
|
|
}
|
|
|
|
if (exif.AtLeastVersion0230 ())
|
|
{
|
|
|
|
if (exif.fSensitivityType != 0)
|
|
{
|
|
|
|
fExifIFD.Add (&fSensitivityType);
|
|
|
|
}
|
|
|
|
// Sensitivity tags. Do not write these extra tags unless the SensitivityType
|
|
// and PhotographicSensitivity (i.e., ISOSpeedRatings) values are valid.
|
|
|
|
if (exif.fSensitivityType != 0 &&
|
|
exif.fISOSpeedRatings [0] != 0)
|
|
{
|
|
|
|
// Standard Output Sensitivity (SOS).
|
|
|
|
if (exif.fStandardOutputSensitivity != 0)
|
|
{
|
|
fExifIFD.Add (&fStandardOutputSensitivity);
|
|
}
|
|
|
|
// Recommended Exposure Index (REI).
|
|
|
|
if (exif.fRecommendedExposureIndex != 0)
|
|
{
|
|
fExifIFD.Add (&fRecommendedExposureIndex);
|
|
}
|
|
|
|
// ISO Speed.
|
|
|
|
if (exif.fISOSpeed != 0)
|
|
{
|
|
|
|
fExifIFD.Add (&fISOSpeed);
|
|
|
|
if (exif.fISOSpeedLatitudeyyy != 0 &&
|
|
exif.fISOSpeedLatitudezzz != 0)
|
|
{
|
|
|
|
fExifIFD.Add (&fISOSpeedLatitudeyyy);
|
|
fExifIFD.Add (&fISOSpeedLatitudezzz);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (exif.fOwnerName.NotEmpty ())
|
|
{
|
|
fExifIFD.Add (&fCameraOwnerName);
|
|
}
|
|
|
|
if (exif.fCameraSerialNumber.NotEmpty ())
|
|
{
|
|
fExifIFD.Add (&fBodySerialNumber);
|
|
}
|
|
|
|
if (exif.fLensInfo [0].IsValid () &&
|
|
exif.fLensInfo [1].IsValid ())
|
|
{
|
|
fExifIFD.Add (&fLensSpecification);
|
|
}
|
|
|
|
if (exif.fLensMake.NotEmpty ())
|
|
{
|
|
fExifIFD.Add (&fLensMake);
|
|
}
|
|
|
|
if (exif.fLensName.NotEmpty ())
|
|
{
|
|
fExifIFD.Add (&fLensModel);
|
|
}
|
|
|
|
if (exif.fLensSerialNumber.NotEmpty ())
|
|
{
|
|
fExifIFD.Add (&fLensSerialNumber);
|
|
}
|
|
|
|
}
|
|
|
|
if (exif.fGPSVersionID)
|
|
{
|
|
|
|
fGPSVersionData [0] = (uint8) (exif.fGPSVersionID >> 24);
|
|
fGPSVersionData [1] = (uint8) (exif.fGPSVersionID >> 16);
|
|
fGPSVersionData [2] = (uint8) (exif.fGPSVersionID >> 8);
|
|
fGPSVersionData [3] = (uint8) (exif.fGPSVersionID );
|
|
|
|
fGPSIFD.Add (&fGPSVersionID);
|
|
|
|
}
|
|
|
|
if (exif.fGPSLatitudeRef.NotEmpty () &&
|
|
exif.fGPSLatitude [0].IsValid ())
|
|
{
|
|
fGPSIFD.Add (&fGPSLatitudeRef);
|
|
fGPSIFD.Add (&fGPSLatitude );
|
|
}
|
|
|
|
if (exif.fGPSLongitudeRef.NotEmpty () &&
|
|
exif.fGPSLongitude [0].IsValid ())
|
|
{
|
|
fGPSIFD.Add (&fGPSLongitudeRef);
|
|
fGPSIFD.Add (&fGPSLongitude );
|
|
}
|
|
|
|
if (exif.fGPSAltitudeRef <= 0x0FF)
|
|
{
|
|
fGPSIFD.Add (&fGPSAltitudeRef);
|
|
}
|
|
|
|
if (exif.fGPSAltitude.IsValid ())
|
|
{
|
|
fGPSIFD.Add (&fGPSAltitude);
|
|
}
|
|
|
|
if (exif.fGPSTimeStamp [0].IsValid ())
|
|
{
|
|
fGPSIFD.Add (&fGPSTimeStamp);
|
|
}
|
|
|
|
if (exif.fGPSSatellites.NotEmpty ())
|
|
{
|
|
fGPSIFD.Add (&fGPSSatellites);
|
|
}
|
|
|
|
if (exif.fGPSStatus.NotEmpty ())
|
|
{
|
|
fGPSIFD.Add (&fGPSStatus);
|
|
}
|
|
|
|
if (exif.fGPSMeasureMode.NotEmpty ())
|
|
{
|
|
fGPSIFD.Add (&fGPSMeasureMode);
|
|
}
|
|
|
|
if (exif.fGPSDOP.IsValid ())
|
|
{
|
|
fGPSIFD.Add (&fGPSDOP);
|
|
}
|
|
|
|
if (exif.fGPSSpeedRef.NotEmpty ())
|
|
{
|
|
fGPSIFD.Add (&fGPSSpeedRef);
|
|
}
|
|
|
|
if (exif.fGPSSpeed.IsValid ())
|
|
{
|
|
fGPSIFD.Add (&fGPSSpeed);
|
|
}
|
|
|
|
if (exif.fGPSTrackRef.NotEmpty ())
|
|
{
|
|
fGPSIFD.Add (&fGPSTrackRef);
|
|
}
|
|
|
|
if (exif.fGPSTrack.IsValid ())
|
|
{
|
|
fGPSIFD.Add (&fGPSTrack);
|
|
}
|
|
|
|
if (exif.fGPSImgDirectionRef.NotEmpty ())
|
|
{
|
|
fGPSIFD.Add (&fGPSImgDirectionRef);
|
|
}
|
|
|
|
if (exif.fGPSImgDirection.IsValid ())
|
|
{
|
|
fGPSIFD.Add (&fGPSImgDirection);
|
|
}
|
|
|
|
if (exif.fGPSMapDatum.NotEmpty ())
|
|
{
|
|
fGPSIFD.Add (&fGPSMapDatum);
|
|
}
|
|
|
|
if (exif.fGPSDestLatitudeRef.NotEmpty () &&
|
|
exif.fGPSDestLatitude [0].IsValid ())
|
|
{
|
|
fGPSIFD.Add (&fGPSDestLatitudeRef);
|
|
fGPSIFD.Add (&fGPSDestLatitude );
|
|
}
|
|
|
|
if (exif.fGPSDestLongitudeRef.NotEmpty () &&
|
|
exif.fGPSDestLongitude [0].IsValid ())
|
|
{
|
|
fGPSIFD.Add (&fGPSDestLongitudeRef);
|
|
fGPSIFD.Add (&fGPSDestLongitude );
|
|
}
|
|
|
|
if (exif.fGPSDestBearingRef.NotEmpty ())
|
|
{
|
|
fGPSIFD.Add (&fGPSDestBearingRef);
|
|
}
|
|
|
|
if (exif.fGPSDestBearing.IsValid ())
|
|
{
|
|
fGPSIFD.Add (&fGPSDestBearing);
|
|
}
|
|
|
|
if (exif.fGPSDestDistanceRef.NotEmpty ())
|
|
{
|
|
fGPSIFD.Add (&fGPSDestDistanceRef);
|
|
}
|
|
|
|
if (exif.fGPSDestDistance.IsValid ())
|
|
{
|
|
fGPSIFD.Add (&fGPSDestDistance);
|
|
}
|
|
|
|
if (exif.fGPSProcessingMethod.NotEmpty ())
|
|
{
|
|
fGPSIFD.Add (&fGPSProcessingMethod);
|
|
}
|
|
|
|
if (exif.fGPSAreaInformation.NotEmpty ())
|
|
{
|
|
fGPSIFD.Add (&fGPSAreaInformation);
|
|
}
|
|
|
|
if (exif.fGPSDateStamp.NotEmpty ())
|
|
{
|
|
fGPSIFD.Add (&fGPSDateStamp);
|
|
}
|
|
|
|
if (exif.fGPSDifferential <= 0x0FFFF)
|
|
{
|
|
fGPSIFD.Add (&fGPSDifferential);
|
|
}
|
|
|
|
if (exif.AtLeastVersion0230 ())
|
|
{
|
|
|
|
if (exif.fGPSHPositioningError.IsValid ())
|
|
{
|
|
fGPSIFD.Add (&fGPSHPositioningError);
|
|
}
|
|
|
|
}
|
|
|
|
AddLinks (directory);
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
void exif_tag_set::AddLinks (dng_tiff_directory &directory)
|
|
{
|
|
|
|
if (fExifIFD.Size () != 0 && !fAddedExifLink)
|
|
{
|
|
|
|
directory.Add (&fExifLink);
|
|
|
|
fAddedExifLink = true;
|
|
|
|
}
|
|
|
|
if (fGPSIFD.Size () != 0 && !fAddedGPSLink)
|
|
{
|
|
|
|
directory.Add (&fGPSLink);
|
|
|
|
fAddedGPSLink = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
class range_tag_set
|
|
{
|
|
|
|
private:
|
|
|
|
uint32 fActiveAreaData [4];
|
|
|
|
tag_uint32_ptr fActiveArea;
|
|
|
|
uint32 fMaskedAreaData [kMaxMaskedAreas * 4];
|
|
|
|
tag_uint32_ptr fMaskedAreas;
|
|
|
|
tag_uint16_ptr fLinearizationTable;
|
|
|
|
uint16 fBlackLevelRepeatDimData [2];
|
|
|
|
tag_uint16_ptr fBlackLevelRepeatDim;
|
|
|
|
dng_urational fBlackLevelData [kMaxBlackPattern *
|
|
kMaxBlackPattern *
|
|
kMaxSamplesPerPixel];
|
|
|
|
tag_urational_ptr fBlackLevel;
|
|
|
|
dng_memory_data fBlackLevelDeltaHData;
|
|
dng_memory_data fBlackLevelDeltaVData;
|
|
|
|
tag_srational_ptr fBlackLevelDeltaH;
|
|
tag_srational_ptr fBlackLevelDeltaV;
|
|
|
|
uint16 fWhiteLevelData16 [kMaxSamplesPerPixel];
|
|
uint32 fWhiteLevelData32 [kMaxSamplesPerPixel];
|
|
|
|
tag_uint16_ptr fWhiteLevel16;
|
|
tag_uint32_ptr fWhiteLevel32;
|
|
|
|
public:
|
|
|
|
range_tag_set (dng_tiff_directory &directory,
|
|
const dng_negative &negative);
|
|
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
range_tag_set::range_tag_set (dng_tiff_directory &directory,
|
|
const dng_negative &negative)
|
|
|
|
: fActiveArea (tcActiveArea,
|
|
fActiveAreaData,
|
|
4)
|
|
|
|
, fMaskedAreas (tcMaskedAreas,
|
|
fMaskedAreaData,
|
|
0)
|
|
|
|
, fLinearizationTable (tcLinearizationTable,
|
|
NULL,
|
|
0)
|
|
|
|
, fBlackLevelRepeatDim (tcBlackLevelRepeatDim,
|
|
fBlackLevelRepeatDimData,
|
|
2)
|
|
|
|
, fBlackLevel (tcBlackLevel,
|
|
fBlackLevelData)
|
|
|
|
, fBlackLevelDeltaHData ()
|
|
, fBlackLevelDeltaVData ()
|
|
|
|
, fBlackLevelDeltaH (tcBlackLevelDeltaH)
|
|
, fBlackLevelDeltaV (tcBlackLevelDeltaV)
|
|
|
|
, fWhiteLevel16 (tcWhiteLevel,
|
|
fWhiteLevelData16)
|
|
|
|
, fWhiteLevel32 (tcWhiteLevel,
|
|
fWhiteLevelData32)
|
|
|
|
{
|
|
|
|
const dng_image &rawImage (negative.RawImage ());
|
|
|
|
const dng_linearization_info *rangeInfo = negative.GetLinearizationInfo ();
|
|
|
|
if (rangeInfo)
|
|
{
|
|
|
|
// ActiveArea:
|
|
|
|
{
|
|
|
|
const dng_rect &r = rangeInfo->fActiveArea;
|
|
|
|
if (r.NotEmpty ())
|
|
{
|
|
|
|
fActiveAreaData [0] = r.t;
|
|
fActiveAreaData [1] = r.l;
|
|
fActiveAreaData [2] = r.b;
|
|
fActiveAreaData [3] = r.r;
|
|
|
|
directory.Add (&fActiveArea);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// MaskedAreas:
|
|
|
|
if (rangeInfo->fMaskedAreaCount)
|
|
{
|
|
|
|
fMaskedAreas.SetCount (rangeInfo->fMaskedAreaCount * 4);
|
|
|
|
for (uint32 index = 0; index < rangeInfo->fMaskedAreaCount; index++)
|
|
{
|
|
|
|
const dng_rect &r = rangeInfo->fMaskedArea [index];
|
|
|
|
fMaskedAreaData [index * 4 + 0] = r.t;
|
|
fMaskedAreaData [index * 4 + 1] = r.l;
|
|
fMaskedAreaData [index * 4 + 2] = r.b;
|
|
fMaskedAreaData [index * 4 + 3] = r.r;
|
|
|
|
}
|
|
|
|
directory.Add (&fMaskedAreas);
|
|
|
|
}
|
|
|
|
// LinearizationTable:
|
|
|
|
if (rangeInfo->fLinearizationTable.Get ())
|
|
{
|
|
|
|
fLinearizationTable.SetData (rangeInfo->fLinearizationTable->Buffer_uint16 () );
|
|
fLinearizationTable.SetCount (rangeInfo->fLinearizationTable->LogicalSize () >> 1);
|
|
|
|
directory.Add (&fLinearizationTable);
|
|
|
|
}
|
|
|
|
// BlackLevelRepeatDim:
|
|
|
|
{
|
|
|
|
fBlackLevelRepeatDimData [0] = (uint16) rangeInfo->fBlackLevelRepeatRows;
|
|
fBlackLevelRepeatDimData [1] = (uint16) rangeInfo->fBlackLevelRepeatCols;
|
|
|
|
directory.Add (&fBlackLevelRepeatDim);
|
|
|
|
}
|
|
|
|
// BlackLevel:
|
|
|
|
{
|
|
|
|
uint32 index = 0;
|
|
|
|
for (uint16 v = 0; v < rangeInfo->fBlackLevelRepeatRows; v++)
|
|
{
|
|
|
|
for (uint32 h = 0; h < rangeInfo->fBlackLevelRepeatCols; h++)
|
|
{
|
|
|
|
for (uint32 c = 0; c < rawImage.Planes (); c++)
|
|
{
|
|
|
|
fBlackLevelData [index++] = rangeInfo->BlackLevel (v, h, c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fBlackLevel.SetCount (rangeInfo->fBlackLevelRepeatRows *
|
|
rangeInfo->fBlackLevelRepeatCols * rawImage.Planes ());
|
|
|
|
directory.Add (&fBlackLevel);
|
|
|
|
}
|
|
|
|
// BlackLevelDeltaH:
|
|
|
|
if (rangeInfo->ColumnBlackCount ())
|
|
{
|
|
|
|
uint32 count = rangeInfo->ColumnBlackCount ();
|
|
|
|
fBlackLevelDeltaHData.Allocate (count, sizeof (dng_srational));
|
|
|
|
dng_srational *blacks = (dng_srational *) fBlackLevelDeltaHData.Buffer ();
|
|
|
|
for (uint32 col = 0; col < count; col++)
|
|
{
|
|
|
|
blacks [col] = rangeInfo->ColumnBlack (col);
|
|
|
|
}
|
|
|
|
fBlackLevelDeltaH.SetData (blacks);
|
|
fBlackLevelDeltaH.SetCount (count );
|
|
|
|
directory.Add (&fBlackLevelDeltaH);
|
|
|
|
}
|
|
|
|
// BlackLevelDeltaV:
|
|
|
|
if (rangeInfo->RowBlackCount ())
|
|
{
|
|
|
|
uint32 count = rangeInfo->RowBlackCount ();
|
|
|
|
fBlackLevelDeltaVData.Allocate (count, sizeof (dng_srational));
|
|
|
|
dng_srational *blacks = (dng_srational *) fBlackLevelDeltaVData.Buffer ();
|
|
|
|
for (uint32 row = 0; row < count; row++)
|
|
{
|
|
|
|
blacks [row] = rangeInfo->RowBlack (row);
|
|
|
|
}
|
|
|
|
fBlackLevelDeltaV.SetData (blacks);
|
|
fBlackLevelDeltaV.SetCount (count );
|
|
|
|
directory.Add (&fBlackLevelDeltaV);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// WhiteLevel:
|
|
|
|
// Only use the 32-bit data type if we must use it since there
|
|
// are some lazy (non-Adobe) DNG readers out there.
|
|
|
|
bool needs32 = false;
|
|
|
|
fWhiteLevel16.SetCount (rawImage.Planes ());
|
|
fWhiteLevel32.SetCount (rawImage.Planes ());
|
|
|
|
for (uint32 c = 0; c < fWhiteLevel16.Count (); c++)
|
|
{
|
|
|
|
fWhiteLevelData32 [c] = negative.WhiteLevel (c);
|
|
|
|
if (fWhiteLevelData32 [c] > 0x0FFFF)
|
|
{
|
|
needs32 = true;
|
|
}
|
|
|
|
fWhiteLevelData16 [c] = (uint16) fWhiteLevelData32 [c];
|
|
|
|
}
|
|
|
|
if (needs32)
|
|
{
|
|
directory.Add (&fWhiteLevel32);
|
|
}
|
|
|
|
else
|
|
{
|
|
directory.Add (&fWhiteLevel16);
|
|
}
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
class mosaic_tag_set
|
|
{
|
|
|
|
private:
|
|
|
|
uint16 fCFARepeatPatternDimData [2];
|
|
|
|
tag_uint16_ptr fCFARepeatPatternDim;
|
|
|
|
uint8 fCFAPatternData [kMaxCFAPattern *
|
|
kMaxCFAPattern];
|
|
|
|
tag_uint8_ptr fCFAPattern;
|
|
|
|
uint8 fCFAPlaneColorData [kMaxColorPlanes];
|
|
|
|
tag_uint8_ptr fCFAPlaneColor;
|
|
|
|
tag_uint16 fCFALayout;
|
|
|
|
tag_uint32 fGreenSplit;
|
|
|
|
public:
|
|
|
|
mosaic_tag_set (dng_tiff_directory &directory,
|
|
const dng_mosaic_info &info);
|
|
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
mosaic_tag_set::mosaic_tag_set (dng_tiff_directory &directory,
|
|
const dng_mosaic_info &info)
|
|
|
|
: fCFARepeatPatternDim (tcCFARepeatPatternDim,
|
|
fCFARepeatPatternDimData,
|
|
2)
|
|
|
|
, fCFAPattern (tcCFAPattern,
|
|
fCFAPatternData)
|
|
|
|
, fCFAPlaneColor (tcCFAPlaneColor,
|
|
fCFAPlaneColorData)
|
|
|
|
, fCFALayout (tcCFALayout,
|
|
(uint16) info.fCFALayout)
|
|
|
|
, fGreenSplit (tcBayerGreenSplit,
|
|
info.fBayerGreenSplit)
|
|
|
|
{
|
|
|
|
if (info.IsColorFilterArray ())
|
|
{
|
|
|
|
// CFARepeatPatternDim:
|
|
|
|
fCFARepeatPatternDimData [0] = (uint16) info.fCFAPatternSize.v;
|
|
fCFARepeatPatternDimData [1] = (uint16) info.fCFAPatternSize.h;
|
|
|
|
directory.Add (&fCFARepeatPatternDim);
|
|
|
|
// CFAPattern:
|
|
|
|
fCFAPattern.SetCount (info.fCFAPatternSize.v *
|
|
info.fCFAPatternSize.h);
|
|
|
|
for (int32 r = 0; r < info.fCFAPatternSize.v; r++)
|
|
{
|
|
|
|
for (int32 c = 0; c < info.fCFAPatternSize.h; c++)
|
|
{
|
|
|
|
fCFAPatternData [r * info.fCFAPatternSize.h + c] = info.fCFAPattern [r] [c];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
directory.Add (&fCFAPattern);
|
|
|
|
// CFAPlaneColor:
|
|
|
|
fCFAPlaneColor.SetCount (info.fColorPlanes);
|
|
|
|
for (uint32 j = 0; j < info.fColorPlanes; j++)
|
|
{
|
|
|
|
fCFAPlaneColorData [j] = info.fCFAPlaneColor [j];
|
|
|
|
}
|
|
|
|
directory.Add (&fCFAPlaneColor);
|
|
|
|
// CFALayout:
|
|
|
|
fCFALayout.Set ((uint16) info.fCFALayout);
|
|
|
|
directory.Add (&fCFALayout);
|
|
|
|
// BayerGreenSplit: (only include if the pattern is a Bayer pattern)
|
|
|
|
if (info.fCFAPatternSize == dng_point (2, 2) &&
|
|
info.fColorPlanes == 3)
|
|
{
|
|
|
|
directory.Add (&fGreenSplit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
class color_tag_set
|
|
{
|
|
|
|
private:
|
|
|
|
uint32 fColorChannels;
|
|
|
|
tag_matrix fCameraCalibration1;
|
|
tag_matrix fCameraCalibration2;
|
|
|
|
tag_string fCameraCalibrationSignature;
|
|
|
|
tag_string fAsShotProfileName;
|
|
|
|
dng_urational fAnalogBalanceData [4];
|
|
|
|
tag_urational_ptr fAnalogBalance;
|
|
|
|
dng_urational fAsShotNeutralData [4];
|
|
|
|
tag_urational_ptr fAsShotNeutral;
|
|
|
|
dng_urational fAsShotWhiteXYData [2];
|
|
|
|
tag_urational_ptr fAsShotWhiteXY;
|
|
|
|
tag_urational fLinearResponseLimit;
|
|
|
|
public:
|
|
|
|
color_tag_set (dng_tiff_directory &directory,
|
|
const dng_negative &negative);
|
|
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
color_tag_set::color_tag_set (dng_tiff_directory &directory,
|
|
const dng_negative &negative)
|
|
|
|
: fColorChannels (negative.ColorChannels ())
|
|
|
|
, fCameraCalibration1 (tcCameraCalibration1,
|
|
negative.CameraCalibration1 ())
|
|
|
|
, fCameraCalibration2 (tcCameraCalibration2,
|
|
negative.CameraCalibration2 ())
|
|
|
|
, fCameraCalibrationSignature (tcCameraCalibrationSignature,
|
|
negative.CameraCalibrationSignature ())
|
|
|
|
, fAsShotProfileName (tcAsShotProfileName,
|
|
negative.AsShotProfileName ())
|
|
|
|
, fAnalogBalance (tcAnalogBalance,
|
|
fAnalogBalanceData,
|
|
fColorChannels)
|
|
|
|
, fAsShotNeutral (tcAsShotNeutral,
|
|
fAsShotNeutralData,
|
|
fColorChannels)
|
|
|
|
, fAsShotWhiteXY (tcAsShotWhiteXY,
|
|
fAsShotWhiteXYData,
|
|
2)
|
|
|
|
, fLinearResponseLimit (tcLinearResponseLimit,
|
|
negative.LinearResponseLimitR ())
|
|
|
|
{
|
|
|
|
if (fColorChannels > 1)
|
|
{
|
|
|
|
uint32 channels2 = fColorChannels * fColorChannels;
|
|
|
|
if (fCameraCalibration1.Count () == channels2)
|
|
{
|
|
|
|
directory.Add (&fCameraCalibration1);
|
|
|
|
}
|
|
|
|
if (fCameraCalibration2.Count () == channels2)
|
|
{
|
|
|
|
directory.Add (&fCameraCalibration2);
|
|
|
|
}
|
|
|
|
if (fCameraCalibration1.Count () == channels2 ||
|
|
fCameraCalibration2.Count () == channels2)
|
|
{
|
|
|
|
if (negative.CameraCalibrationSignature ().NotEmpty ())
|
|
{
|
|
|
|
directory.Add (&fCameraCalibrationSignature);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (negative.AsShotProfileName ().NotEmpty ())
|
|
{
|
|
|
|
directory.Add (&fAsShotProfileName);
|
|
|
|
}
|
|
|
|
for (uint32 j = 0; j < fColorChannels; j++)
|
|
{
|
|
|
|
fAnalogBalanceData [j] = negative.AnalogBalanceR (j);
|
|
|
|
}
|
|
|
|
directory.Add (&fAnalogBalance);
|
|
|
|
if (negative.HasCameraNeutral ())
|
|
{
|
|
|
|
for (uint32 k = 0; k < fColorChannels; k++)
|
|
{
|
|
|
|
fAsShotNeutralData [k] = negative.CameraNeutralR (k);
|
|
|
|
}
|
|
|
|
directory.Add (&fAsShotNeutral);
|
|
|
|
}
|
|
|
|
else if (negative.HasCameraWhiteXY ())
|
|
{
|
|
|
|
negative.GetCameraWhiteXY (fAsShotWhiteXYData [0],
|
|
fAsShotWhiteXYData [1]);
|
|
|
|
directory.Add (&fAsShotWhiteXY);
|
|
|
|
}
|
|
|
|
directory.Add (&fLinearResponseLimit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
class profile_tag_set
|
|
{
|
|
|
|
private:
|
|
|
|
tag_uint16 fCalibrationIlluminant1;
|
|
tag_uint16 fCalibrationIlluminant2;
|
|
|
|
tag_matrix fColorMatrix1;
|
|
tag_matrix fColorMatrix2;
|
|
|
|
tag_matrix fForwardMatrix1;
|
|
tag_matrix fForwardMatrix2;
|
|
|
|
tag_matrix fReductionMatrix1;
|
|
tag_matrix fReductionMatrix2;
|
|
|
|
tag_string fProfileName;
|
|
|
|
tag_string fProfileCalibrationSignature;
|
|
|
|
tag_uint32 fEmbedPolicyTag;
|
|
|
|
tag_string fCopyrightTag;
|
|
|
|
uint32 fHueSatMapDimData [3];
|
|
|
|
tag_uint32_ptr fHueSatMapDims;
|
|
|
|
tag_data_ptr fHueSatData1;
|
|
tag_data_ptr fHueSatData2;
|
|
|
|
tag_uint32 fHueSatMapEncodingTag;
|
|
|
|
uint32 fLookTableDimData [3];
|
|
|
|
tag_uint32_ptr fLookTableDims;
|
|
|
|
tag_data_ptr fLookTableData;
|
|
|
|
tag_uint32 fLookTableEncodingTag;
|
|
|
|
tag_srational fBaselineExposureOffsetTag;
|
|
|
|
tag_uint32 fDefaultBlackRenderTag;
|
|
|
|
dng_memory_data fToneCurveBuffer;
|
|
|
|
tag_data_ptr fToneCurveTag;
|
|
|
|
public:
|
|
|
|
profile_tag_set (dng_tiff_directory &directory,
|
|
const dng_camera_profile &profile);
|
|
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
profile_tag_set::profile_tag_set (dng_tiff_directory &directory,
|
|
const dng_camera_profile &profile)
|
|
|
|
: fCalibrationIlluminant1 (tcCalibrationIlluminant1,
|
|
(uint16) profile.CalibrationIlluminant1 ())
|
|
|
|
, fCalibrationIlluminant2 (tcCalibrationIlluminant2,
|
|
(uint16) profile.CalibrationIlluminant2 ())
|
|
|
|
, fColorMatrix1 (tcColorMatrix1,
|
|
profile.ColorMatrix1 ())
|
|
|
|
, fColorMatrix2 (tcColorMatrix2,
|
|
profile.ColorMatrix2 ())
|
|
|
|
, fForwardMatrix1 (tcForwardMatrix1,
|
|
profile.ForwardMatrix1 ())
|
|
|
|
, fForwardMatrix2 (tcForwardMatrix2,
|
|
profile.ForwardMatrix2 ())
|
|
|
|
, fReductionMatrix1 (tcReductionMatrix1,
|
|
profile.ReductionMatrix1 ())
|
|
|
|
, fReductionMatrix2 (tcReductionMatrix2,
|
|
profile.ReductionMatrix2 ())
|
|
|
|
, fProfileName (tcProfileName,
|
|
profile.Name (),
|
|
false)
|
|
|
|
, fProfileCalibrationSignature (tcProfileCalibrationSignature,
|
|
profile.ProfileCalibrationSignature (),
|
|
false)
|
|
|
|
, fEmbedPolicyTag (tcProfileEmbedPolicy,
|
|
profile.EmbedPolicy ())
|
|
|
|
, fCopyrightTag (tcProfileCopyright,
|
|
profile.Copyright (),
|
|
false)
|
|
|
|
, fHueSatMapDims (tcProfileHueSatMapDims,
|
|
fHueSatMapDimData,
|
|
3)
|
|
|
|
, fHueSatData1 (tcProfileHueSatMapData1,
|
|
ttFloat,
|
|
profile.HueSatDeltas1 ().DeltasCount () * 3,
|
|
profile.HueSatDeltas1 ().GetConstDeltas ())
|
|
|
|
, fHueSatData2 (tcProfileHueSatMapData2,
|
|
ttFloat,
|
|
profile.HueSatDeltas2 ().DeltasCount () * 3,
|
|
profile.HueSatDeltas2 ().GetConstDeltas ())
|
|
|
|
, fHueSatMapEncodingTag (tcProfileHueSatMapEncoding,
|
|
profile.HueSatMapEncoding ())
|
|
|
|
, fLookTableDims (tcProfileLookTableDims,
|
|
fLookTableDimData,
|
|
3)
|
|
|
|
, fLookTableData (tcProfileLookTableData,
|
|
ttFloat,
|
|
profile.LookTable ().DeltasCount () * 3,
|
|
profile.LookTable ().GetConstDeltas ())
|
|
|
|
, fLookTableEncodingTag (tcProfileLookTableEncoding,
|
|
profile.LookTableEncoding ())
|
|
|
|
, fBaselineExposureOffsetTag (tcBaselineExposureOffset,
|
|
profile.BaselineExposureOffset ())
|
|
|
|
, fDefaultBlackRenderTag (tcDefaultBlackRender,
|
|
profile.DefaultBlackRender ())
|
|
|
|
, fToneCurveBuffer ()
|
|
|
|
, fToneCurveTag (tcProfileToneCurve,
|
|
ttFloat,
|
|
0,
|
|
NULL)
|
|
|
|
{
|
|
|
|
if (profile.HasColorMatrix1 ())
|
|
{
|
|
|
|
uint32 colorChannels = profile.ColorMatrix1 ().Rows ();
|
|
|
|
directory.Add (&fCalibrationIlluminant1);
|
|
|
|
directory.Add (&fColorMatrix1);
|
|
|
|
if (fForwardMatrix1.Count () == colorChannels * 3)
|
|
{
|
|
|
|
directory.Add (&fForwardMatrix1);
|
|
|
|
}
|
|
|
|
if (colorChannels > 3 && fReductionMatrix1.Count () == colorChannels * 3)
|
|
{
|
|
|
|
directory.Add (&fReductionMatrix1);
|
|
|
|
}
|
|
|
|
if (profile.HasColorMatrix2 ())
|
|
{
|
|
|
|
directory.Add (&fCalibrationIlluminant2);
|
|
|
|
directory.Add (&fColorMatrix2);
|
|
|
|
if (fForwardMatrix2.Count () == colorChannels * 3)
|
|
{
|
|
|
|
directory.Add (&fForwardMatrix2);
|
|
|
|
}
|
|
|
|
if (colorChannels > 3 && fReductionMatrix2.Count () == colorChannels * 3)
|
|
{
|
|
|
|
directory.Add (&fReductionMatrix2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (profile.Name ().NotEmpty ())
|
|
{
|
|
|
|
directory.Add (&fProfileName);
|
|
|
|
}
|
|
|
|
if (profile.ProfileCalibrationSignature ().NotEmpty ())
|
|
{
|
|
|
|
directory.Add (&fProfileCalibrationSignature);
|
|
|
|
}
|
|
|
|
directory.Add (&fEmbedPolicyTag);
|
|
|
|
if (profile.Copyright ().NotEmpty ())
|
|
{
|
|
|
|
directory.Add (&fCopyrightTag);
|
|
|
|
}
|
|
|
|
bool haveHueSat1 = profile.HueSatDeltas1 ().IsValid ();
|
|
|
|
bool haveHueSat2 = profile.HueSatDeltas2 ().IsValid () &&
|
|
profile.HasColorMatrix2 ();
|
|
|
|
if (haveHueSat1 || haveHueSat2)
|
|
{
|
|
|
|
uint32 hueDivs = 0;
|
|
uint32 satDivs = 0;
|
|
uint32 valDivs = 0;
|
|
|
|
if (haveHueSat1)
|
|
{
|
|
|
|
profile.HueSatDeltas1 ().GetDivisions (hueDivs,
|
|
satDivs,
|
|
valDivs);
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
|
|
profile.HueSatDeltas2 ().GetDivisions (hueDivs,
|
|
satDivs,
|
|
valDivs);
|
|
|
|
}
|
|
|
|
fHueSatMapDimData [0] = hueDivs;
|
|
fHueSatMapDimData [1] = satDivs;
|
|
fHueSatMapDimData [2] = valDivs;
|
|
|
|
directory.Add (&fHueSatMapDims);
|
|
|
|
// Don't bother including the ProfileHueSatMapEncoding tag unless it's
|
|
// non-linear.
|
|
|
|
if (profile.HueSatMapEncoding () != encoding_Linear)
|
|
{
|
|
|
|
directory.Add (&fHueSatMapEncodingTag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (haveHueSat1)
|
|
{
|
|
|
|
directory.Add (&fHueSatData1);
|
|
|
|
}
|
|
|
|
if (haveHueSat2)
|
|
{
|
|
|
|
directory.Add (&fHueSatData2);
|
|
|
|
}
|
|
|
|
if (profile.HasLookTable ())
|
|
{
|
|
|
|
uint32 hueDivs = 0;
|
|
uint32 satDivs = 0;
|
|
uint32 valDivs = 0;
|
|
|
|
profile.LookTable ().GetDivisions (hueDivs,
|
|
satDivs,
|
|
valDivs);
|
|
|
|
fLookTableDimData [0] = hueDivs;
|
|
fLookTableDimData [1] = satDivs;
|
|
fLookTableDimData [2] = valDivs;
|
|
|
|
directory.Add (&fLookTableDims);
|
|
|
|
directory.Add (&fLookTableData);
|
|
|
|
// Don't bother including the ProfileLookTableEncoding tag unless it's
|
|
// non-linear.
|
|
|
|
if (profile.LookTableEncoding () != encoding_Linear)
|
|
{
|
|
|
|
directory.Add (&fLookTableEncodingTag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Don't bother including the BaselineExposureOffset tag unless it's both
|
|
// valid and non-zero.
|
|
|
|
if (profile.BaselineExposureOffset ().IsValid ())
|
|
{
|
|
|
|
if (profile.BaselineExposureOffset ().As_real64 () != 0.0)
|
|
{
|
|
|
|
directory.Add (&fBaselineExposureOffsetTag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (profile.DefaultBlackRender () != defaultBlackRender_Auto)
|
|
{
|
|
|
|
directory.Add (&fDefaultBlackRenderTag);
|
|
|
|
}
|
|
|
|
if (profile.ToneCurve ().IsValid ())
|
|
{
|
|
|
|
// Tone curve stored as pairs of 32-bit coordinates. Probably could do with
|
|
// 16-bits here, but should be small number of points so...
|
|
|
|
uint32 toneCurvePoints = (uint32) (profile.ToneCurve ().fCoord.size ());
|
|
|
|
fToneCurveBuffer.Allocate (SafeUint32Mult(toneCurvePoints, 2),
|
|
sizeof (real32));
|
|
|
|
real32 *points = fToneCurveBuffer.Buffer_real32 ();
|
|
|
|
fToneCurveTag.SetCount (toneCurvePoints * 2);
|
|
fToneCurveTag.SetData (points);
|
|
|
|
for (uint32 i = 0; i < toneCurvePoints; i++)
|
|
{
|
|
|
|
// Transpose coordinates so they are in a more expected
|
|
// order (domain -> range).
|
|
|
|
points [i * 2 ] = (real32) profile.ToneCurve ().fCoord [i].h;
|
|
points [i * 2 + 1] = (real32) profile.ToneCurve ().fCoord [i].v;
|
|
|
|
}
|
|
|
|
directory.Add (&fToneCurveTag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
tiff_dng_extended_color_profile::tiff_dng_extended_color_profile
|
|
(const dng_camera_profile &profile)
|
|
|
|
: fProfile (profile)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
void tiff_dng_extended_color_profile::Put (dng_stream &stream,
|
|
bool includeModelRestriction)
|
|
{
|
|
|
|
// Profile header.
|
|
|
|
stream.Put_uint16 (stream.BigEndian () ? byteOrderMM : byteOrderII);
|
|
|
|
stream.Put_uint16 (magicExtendedProfile);
|
|
|
|
stream.Put_uint32 (8);
|
|
|
|
// Profile tags.
|
|
|
|
profile_tag_set tagSet (*this, fProfile);
|
|
|
|
// Camera this profile is for.
|
|
|
|
tag_string cameraModelTag (tcUniqueCameraModel,
|
|
fProfile.UniqueCameraModelRestriction ());
|
|
|
|
if (includeModelRestriction)
|
|
{
|
|
|
|
if (fProfile.UniqueCameraModelRestriction ().NotEmpty ())
|
|
{
|
|
|
|
Add (&cameraModelTag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Write it all out.
|
|
|
|
dng_tiff_directory::Put (stream, offsetsRelativeToExplicitBase, 8);
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
tag_dng_noise_profile::tag_dng_noise_profile (const dng_noise_profile &profile)
|
|
|
|
: tag_data_ptr (tcNoiseProfile,
|
|
ttDouble,
|
|
2 * profile.NumFunctions (),
|
|
fValues)
|
|
|
|
{
|
|
|
|
DNG_REQUIRE (profile.NumFunctions () <= kMaxColorPlanes,
|
|
"Too many noise functions in tag_dng_noise_profile.");
|
|
|
|
for (uint32 i = 0; i < profile.NumFunctions (); i++)
|
|
{
|
|
|
|
fValues [(2 * i) ] = profile.NoiseFunction (i).Scale ();
|
|
fValues [(2 * i) + 1] = profile.NoiseFunction (i).Offset ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
dng_image_writer::dng_image_writer ()
|
|
{
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
dng_image_writer::~dng_image_writer ()
|
|
{
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
uint32 dng_image_writer::CompressedBufferSize (const dng_ifd &ifd,
|
|
uint32 uncompressedSize)
|
|
{
|
|
|
|
switch (ifd.fCompression)
|
|
{
|
|
|
|
case ccLZW:
|
|
{
|
|
|
|
// Add lots of slop for LZW to expand data.
|
|
|
|
return SafeUint32Add (SafeUint32Mult (uncompressedSize, 2), 1024);
|
|
|
|
}
|
|
|
|
case ccDeflate:
|
|
{
|
|
|
|
// ZLib says maximum is source size + 0.1% + 12 bytes.
|
|
|
|
return SafeUint32Add (SafeUint32Add (uncompressedSize,
|
|
uncompressedSize >> 8), 64);
|
|
|
|
}
|
|
|
|
case ccJPEG:
|
|
{
|
|
|
|
// If we are saving lossless JPEG from an 8-bit image, reserve
|
|
// space to pad the data out to 16-bits.
|
|
|
|
if (ifd.fBitsPerSample [0] <= 8)
|
|
{
|
|
|
|
return SafeUint32Mult (uncompressedSize, 2);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
static void EncodeDelta8 (uint8 *dPtr,
|
|
uint32 rows,
|
|
uint32 cols,
|
|
uint32 channels)
|
|
{
|
|
|
|
const uint32 dRowStep = cols * channels;
|
|
|
|
for (uint32 row = 0; row < rows; row++)
|
|
{
|
|
|
|
for (uint32 col = cols - 1; col > 0; col--)
|
|
{
|
|
|
|
for (uint32 channel = 0; channel < channels; channel++)
|
|
{
|
|
|
|
dPtr [col * channels + channel] -= dPtr [(col - 1) * channels + channel];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dPtr += dRowStep;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
static void EncodeDelta16 (uint16 *dPtr,
|
|
uint32 rows,
|
|
uint32 cols,
|
|
uint32 channels)
|
|
{
|
|
|
|
const uint32 dRowStep = cols * channels;
|
|
|
|
for (uint32 row = 0; row < rows; row++)
|
|
{
|
|
|
|
for (uint32 col = cols - 1; col > 0; col--)
|
|
{
|
|
|
|
for (uint32 channel = 0; channel < channels; channel++)
|
|
{
|
|
|
|
dPtr [col * channels + channel] -= dPtr [(col - 1) * channels + channel];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dPtr += dRowStep;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
static void EncodeDelta32 (uint32 *dPtr,
|
|
uint32 rows,
|
|
uint32 cols,
|
|
uint32 channels)
|
|
{
|
|
|
|
const uint32 dRowStep = cols * channels;
|
|
|
|
for (uint32 row = 0; row < rows; row++)
|
|
{
|
|
|
|
for (uint32 col = cols - 1; col > 0; col--)
|
|
{
|
|
|
|
for (uint32 channel = 0; channel < channels; channel++)
|
|
{
|
|
|
|
dPtr [col * channels + channel] -= dPtr [(col - 1) * channels + channel];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dPtr += dRowStep;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
inline void EncodeDeltaBytes (uint8 *bytePtr, int32 cols, int32 channels)
|
|
{
|
|
|
|
if (channels == 1)
|
|
{
|
|
|
|
bytePtr += (cols - 1);
|
|
|
|
uint8 this0 = bytePtr [0];
|
|
|
|
for (int32 col = 1; col < cols; col++)
|
|
{
|
|
|
|
uint8 prev0 = bytePtr [-1];
|
|
|
|
this0 -= prev0;
|
|
|
|
bytePtr [0] = this0;
|
|
|
|
this0 = prev0;
|
|
|
|
bytePtr -= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (channels == 3)
|
|
{
|
|
|
|
bytePtr += (cols - 1) * 3;
|
|
|
|
uint8 this0 = bytePtr [0];
|
|
uint8 this1 = bytePtr [1];
|
|
uint8 this2 = bytePtr [2];
|
|
|
|
for (int32 col = 1; col < cols; col++)
|
|
{
|
|
|
|
uint8 prev0 = bytePtr [-3];
|
|
uint8 prev1 = bytePtr [-2];
|
|
uint8 prev2 = bytePtr [-1];
|
|
|
|
this0 -= prev0;
|
|
this1 -= prev1;
|
|
this2 -= prev2;
|
|
|
|
bytePtr [0] = this0;
|
|
bytePtr [1] = this1;
|
|
bytePtr [2] = this2;
|
|
|
|
this0 = prev0;
|
|
this1 = prev1;
|
|
this2 = prev2;
|
|
|
|
bytePtr -= 3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
|
|
uint32 rowBytes = cols * channels;
|
|
|
|
bytePtr += rowBytes - 1;
|
|
|
|
for (uint32 col = channels; col < rowBytes; col++)
|
|
{
|
|
|
|
bytePtr [0] -= bytePtr [-channels];
|
|
|
|
bytePtr--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void EncodeFPDelta (uint8 *buffer,
|
|
uint8 *temp,
|
|
int32 cols,
|
|
int32 channels,
|
|
int32 bytesPerSample)
|
|
{
|
|
|
|
int32 rowIncrement = cols * channels;
|
|
|
|
if (bytesPerSample == 2)
|
|
{
|
|
|
|
const uint8 *src = buffer;
|
|
|
|
#if qDNGBigEndian
|
|
uint8 *dst0 = temp;
|
|
uint8 *dst1 = temp + rowIncrement;
|
|
#else
|
|
uint8 *dst1 = temp;
|
|
uint8 *dst0 = temp + rowIncrement;
|
|
#endif
|
|
|
|
for (int32 col = 0; col < rowIncrement; ++col)
|
|
{
|
|
|
|
dst0 [col] = src [0];
|
|
dst1 [col] = src [1];
|
|
|
|
src += 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (bytesPerSample == 3)
|
|
{
|
|
|
|
const uint8 *src = buffer;
|
|
|
|
uint8 *dst0 = temp;
|
|
uint8 *dst1 = temp + rowIncrement;
|
|
uint8 *dst2 = temp + rowIncrement * 2;
|
|
|
|
for (int32 col = 0; col < rowIncrement; ++col)
|
|
{
|
|
|
|
dst0 [col] = src [0];
|
|
dst1 [col] = src [1];
|
|
dst2 [col] = src [2];
|
|
|
|
src += 3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
|
|
const uint8 *src = buffer;
|
|
|
|
#if qDNGBigEndian
|
|
uint8 *dst0 = temp;
|
|
uint8 *dst1 = temp + rowIncrement;
|
|
uint8 *dst2 = temp + rowIncrement * 2;
|
|
uint8 *dst3 = temp + rowIncrement * 3;
|
|
#else
|
|
uint8 *dst3 = temp;
|
|
uint8 *dst2 = temp + rowIncrement;
|
|
uint8 *dst1 = temp + rowIncrement * 2;
|
|
uint8 *dst0 = temp + rowIncrement * 3;
|
|
#endif
|
|
|
|
for (int32 col = 0; col < rowIncrement; ++col)
|
|
{
|
|
|
|
dst0 [col] = src [0];
|
|
dst1 [col] = src [1];
|
|
dst2 [col] = src [2];
|
|
dst3 [col] = src [3];
|
|
|
|
src += 4;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
EncodeDeltaBytes (temp, cols*bytesPerSample, channels);
|
|
|
|
memcpy (buffer, temp, cols*bytesPerSample*channels);
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void dng_image_writer::EncodePredictor (dng_host &host,
|
|
const dng_ifd &ifd,
|
|
dng_pixel_buffer &buffer,
|
|
AutoPtr<dng_memory_block> &tempBuffer)
|
|
{
|
|
|
|
switch (ifd.fPredictor)
|
|
{
|
|
|
|
case cpHorizontalDifference:
|
|
case cpHorizontalDifferenceX2:
|
|
case cpHorizontalDifferenceX4:
|
|
{
|
|
|
|
int32 xFactor = 1;
|
|
|
|
if (ifd.fPredictor == cpHorizontalDifferenceX2)
|
|
{
|
|
xFactor = 2;
|
|
}
|
|
|
|
else if (ifd.fPredictor == cpHorizontalDifferenceX4)
|
|
{
|
|
xFactor = 4;
|
|
}
|
|
|
|
switch (buffer.fPixelType)
|
|
{
|
|
|
|
case ttByte:
|
|
{
|
|
|
|
EncodeDelta8 ((uint8 *) buffer.fData,
|
|
buffer.fArea.H (),
|
|
buffer.fArea.W () / xFactor,
|
|
buffer.fPlanes * xFactor);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
case ttShort:
|
|
{
|
|
|
|
EncodeDelta16 ((uint16 *) buffer.fData,
|
|
buffer.fArea.H (),
|
|
buffer.fArea.W () / xFactor,
|
|
buffer.fPlanes * xFactor);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
case ttLong:
|
|
{
|
|
|
|
EncodeDelta32 ((uint32 *) buffer.fData,
|
|
buffer.fArea.H (),
|
|
buffer.fArea.W () / xFactor,
|
|
buffer.fPlanes * xFactor);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case cpFloatingPoint:
|
|
case cpFloatingPointX2:
|
|
case cpFloatingPointX4:
|
|
{
|
|
|
|
int32 xFactor = 1;
|
|
|
|
if (ifd.fPredictor == cpFloatingPointX2)
|
|
{
|
|
xFactor = 2;
|
|
}
|
|
|
|
else if (ifd.fPredictor == cpFloatingPointX4)
|
|
{
|
|
xFactor = 4;
|
|
}
|
|
|
|
if (buffer.fRowStep < 0)
|
|
{
|
|
ThrowProgramError ("Row step may not be negative");
|
|
}
|
|
uint32 tempBufferSize = SafeUint32Mult (
|
|
static_cast<uint32>(buffer.fRowStep),
|
|
buffer.fPixelSize);
|
|
|
|
if (!tempBuffer.Get () || tempBuffer->LogicalSize () < tempBufferSize)
|
|
{
|
|
|
|
tempBuffer.Reset (host.Allocate (tempBufferSize));
|
|
|
|
}
|
|
|
|
for (int32 row = buffer.fArea.t; row < buffer.fArea.b; row++)
|
|
{
|
|
|
|
EncodeFPDelta ((uint8 *) buffer.DirtyPixel (row, buffer.fArea.l, buffer.fPlane),
|
|
tempBuffer->Buffer_uint8 (),
|
|
buffer.fArea.W () / xFactor,
|
|
buffer.fPlanes * xFactor,
|
|
buffer.fPixelSize);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
|
|
if (ifd.fPredictor != cpNullPredictor)
|
|
{
|
|
|
|
ThrowProgramError ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void dng_image_writer::ByteSwapBuffer (dng_host & /* host */,
|
|
dng_pixel_buffer &buffer)
|
|
{
|
|
|
|
uint32 pixels = buffer.fRowStep * buffer.fArea.H ();
|
|
|
|
switch (buffer.fPixelSize)
|
|
{
|
|
|
|
case 2:
|
|
{
|
|
|
|
DoSwapBytes16 ((uint16 *) buffer.fData,
|
|
pixels);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 4:
|
|
{
|
|
|
|
DoSwapBytes32 ((uint32 *) buffer.fData,
|
|
pixels);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void dng_image_writer::ReorderSubTileBlocks (const dng_ifd &ifd,
|
|
dng_pixel_buffer &buffer,
|
|
AutoPtr<dng_memory_block> &uncompressedBuffer,
|
|
AutoPtr<dng_memory_block> &subTileBlockBuffer)
|
|
{
|
|
|
|
uint32 blockRows = ifd.fSubTileBlockRows;
|
|
uint32 blockCols = ifd.fSubTileBlockCols;
|
|
|
|
uint32 rowBlocks = buffer.fArea.H () / blockRows;
|
|
uint32 colBlocks = buffer.fArea.W () / blockCols;
|
|
|
|
int32 rowStep = buffer.fRowStep * buffer.fPixelSize;
|
|
int32 colStep = buffer.fColStep * buffer.fPixelSize;
|
|
|
|
int32 rowBlockStep = rowStep * blockRows;
|
|
int32 colBlockStep = colStep * blockCols;
|
|
|
|
uint32 blockColBytes = blockCols * buffer.fPlanes * buffer.fPixelSize;
|
|
|
|
const uint8 *s0 = uncompressedBuffer->Buffer_uint8 ();
|
|
uint8 *d0 = subTileBlockBuffer->Buffer_uint8 ();
|
|
|
|
for (uint32 rowBlock = 0; rowBlock < rowBlocks; rowBlock++)
|
|
{
|
|
|
|
const uint8 *s1 = s0;
|
|
|
|
for (uint32 colBlock = 0; colBlock < colBlocks; colBlock++)
|
|
{
|
|
|
|
const uint8 *s2 = s1;
|
|
|
|
for (uint32 blockRow = 0; blockRow < blockRows; blockRow++)
|
|
{
|
|
|
|
for (uint32 j = 0; j < blockColBytes; j++)
|
|
{
|
|
|
|
d0 [j] = s2 [j];
|
|
|
|
}
|
|
|
|
d0 += blockColBytes;
|
|
|
|
s2 += rowStep;
|
|
|
|
}
|
|
|
|
s1 += colBlockStep;
|
|
|
|
}
|
|
|
|
s0 += rowBlockStep;
|
|
|
|
}
|
|
|
|
// Copy back reordered pixels.
|
|
|
|
DoCopyBytes (subTileBlockBuffer->Buffer (),
|
|
uncompressedBuffer->Buffer (),
|
|
uncompressedBuffer->LogicalSize ());
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
class dng_lzw_compressor
|
|
{
|
|
|
|
private:
|
|
|
|
enum
|
|
{
|
|
kResetCode = 256,
|
|
kEndCode = 257,
|
|
kTableSize = 4096
|
|
};
|
|
|
|
// Compressor nodes have two son pointers. The low order bit of
|
|
// the next code determines which pointer is used. This cuts the
|
|
// number of nodes searched for the next code by two on average.
|
|
|
|
struct LZWCompressorNode
|
|
{
|
|
int16 final;
|
|
int16 son0;
|
|
int16 son1;
|
|
int16 brother;
|
|
};
|
|
|
|
dng_memory_data fBuffer;
|
|
|
|
LZWCompressorNode *fTable;
|
|
|
|
uint8 *fDstPtr;
|
|
|
|
int32 fDstCount;
|
|
|
|
int32 fBitOffset;
|
|
|
|
int32 fNextCode;
|
|
|
|
int32 fCodeSize;
|
|
|
|
public:
|
|
|
|
dng_lzw_compressor ();
|
|
|
|
void Compress (const uint8 *sPtr,
|
|
uint8 *dPtr,
|
|
uint32 sCount,
|
|
uint32 &dCount);
|
|
|
|
private:
|
|
|
|
void InitTable ();
|
|
|
|
int32 SearchTable (int32 w, int32 k) const
|
|
{
|
|
|
|
DNG_ASSERT ((w >= 0) && (w <= kTableSize),
|
|
"Bad w value in dng_lzw_compressor::SearchTable");
|
|
|
|
int32 son0 = fTable [w] . son0;
|
|
int32 son1 = fTable [w] . son1;
|
|
|
|
// Branchless version of:
|
|
// int32 code = (k & 1) ? son1 : son0;
|
|
|
|
int32 code = son0 + ((-((int32) (k & 1))) & (son1 - son0));
|
|
|
|
while (code > 0 && fTable [code].final != k)
|
|
{
|
|
code = fTable [code].brother;
|
|
}
|
|
|
|
return code;
|
|
|
|
}
|
|
|
|
void AddTable (int32 w, int32 k);
|
|
|
|
void PutCodeWord (int32 code);
|
|
|
|
// Hidden copy constructor and assignment operator.
|
|
|
|
dng_lzw_compressor (const dng_lzw_compressor &compressor);
|
|
|
|
dng_lzw_compressor & operator= (const dng_lzw_compressor &compressor);
|
|
|
|
};
|
|
|
|
/******************************************************************************/
|
|
|
|
dng_lzw_compressor::dng_lzw_compressor ()
|
|
|
|
: fBuffer ()
|
|
, fTable (NULL)
|
|
, fDstPtr (NULL)
|
|
, fDstCount (0)
|
|
, fBitOffset (0)
|
|
, fNextCode (0)
|
|
, fCodeSize (0)
|
|
|
|
{
|
|
|
|
fBuffer.Allocate (kTableSize, sizeof (LZWCompressorNode));
|
|
|
|
fTable = (LZWCompressorNode *) fBuffer.Buffer ();
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
void dng_lzw_compressor::InitTable ()
|
|
{
|
|
|
|
fCodeSize = 9;
|
|
|
|
fNextCode = 258;
|
|
|
|
LZWCompressorNode *node = &fTable [0];
|
|
|
|
for (int32 code = 0; code < 256; ++code)
|
|
{
|
|
|
|
node->final = (int16) code;
|
|
node->son0 = -1;
|
|
node->son1 = -1;
|
|
node->brother = -1;
|
|
|
|
node++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
void dng_lzw_compressor::AddTable (int32 w, int32 k)
|
|
{
|
|
|
|
DNG_ASSERT ((w >= 0) && (w <= kTableSize),
|
|
"Bad w value in dng_lzw_compressor::AddTable");
|
|
|
|
LZWCompressorNode *node = &fTable [w];
|
|
|
|
int32 nextCode = fNextCode;
|
|
|
|
DNG_ASSERT ((nextCode >= 0) && (nextCode <= kTableSize),
|
|
"Bad fNextCode value in dng_lzw_compressor::AddTable");
|
|
|
|
LZWCompressorNode *node2 = &fTable [nextCode];
|
|
|
|
fNextCode++;
|
|
|
|
int32 oldSon;
|
|
|
|
if( k&1 )
|
|
{
|
|
oldSon = node->son1;
|
|
node->son1 = (int16) nextCode;
|
|
}
|
|
else
|
|
{
|
|
oldSon = node->son0;
|
|
node->son0 = (int16) nextCode;
|
|
}
|
|
|
|
node2->final = (int16) k;
|
|
node2->son0 = -1;
|
|
node2->son1 = -1;
|
|
node2->brother = (int16) oldSon;
|
|
|
|
if (nextCode == (1 << fCodeSize) - 1)
|
|
{
|
|
if (fCodeSize != 12)
|
|
fCodeSize++;
|
|
}
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
void dng_lzw_compressor::PutCodeWord (int32 code)
|
|
{
|
|
|
|
int32 bit = (int32) (fBitOffset & 7);
|
|
|
|
int32 offset1 = fBitOffset >> 3;
|
|
int32 offset2 = (fBitOffset + fCodeSize - 1) >> 3;
|
|
|
|
int32 shift1 = (fCodeSize + bit) - 8;
|
|
int32 shift2 = (fCodeSize + bit) - 16;
|
|
|
|
uint8 byte1 = (uint8) (code >> shift1);
|
|
|
|
uint8 *dstPtr1 = fDstPtr + offset1;
|
|
uint8 *dstPtr3 = fDstPtr + offset2;
|
|
|
|
if (offset1 + 1 == offset2)
|
|
{
|
|
|
|
uint8 byte2 = (uint8) (code << (-shift2));
|
|
|
|
if (bit)
|
|
*dstPtr1 |= byte1;
|
|
else
|
|
*dstPtr1 = byte1;
|
|
|
|
*dstPtr3 = byte2;
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
|
|
int32 shift3 = (fCodeSize + bit) - 24;
|
|
|
|
uint8 byte2 = (uint8) (code >> shift2);
|
|
uint8 byte3 = (uint8) (code << (-shift3));
|
|
|
|
uint8 *dstPtr2 = fDstPtr + (offset1 + 1);
|
|
|
|
if (bit)
|
|
*dstPtr1 |= byte1;
|
|
else
|
|
*dstPtr1 = byte1;
|
|
|
|
*dstPtr2 = byte2;
|
|
|
|
*dstPtr3 = byte3;
|
|
|
|
}
|
|
|
|
fBitOffset += fCodeSize;
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
void dng_lzw_compressor::Compress (const uint8 *sPtr,
|
|
uint8 *dPtr,
|
|
uint32 sCount,
|
|
uint32 &dCount)
|
|
{
|
|
|
|
fDstPtr = dPtr;
|
|
|
|
fBitOffset = 0;
|
|
|
|
InitTable ();
|
|
|
|
PutCodeWord (kResetCode);
|
|
|
|
int32 code = -1;
|
|
|
|
int32 pixel;
|
|
|
|
if (sCount > 0)
|
|
{
|
|
|
|
pixel = *sPtr;
|
|
sPtr = sPtr + 1;
|
|
code = pixel;
|
|
|
|
sCount--;
|
|
|
|
while (sCount--)
|
|
{
|
|
|
|
pixel = *sPtr;
|
|
sPtr = sPtr + 1;
|
|
|
|
int32 newCode = SearchTable (code, pixel);
|
|
|
|
if (newCode == -1)
|
|
{
|
|
|
|
PutCodeWord (code);
|
|
|
|
if (fNextCode < 4093)
|
|
{
|
|
AddTable (code, pixel);
|
|
}
|
|
else
|
|
{
|
|
PutCodeWord (kResetCode);
|
|
InitTable ();
|
|
}
|
|
|
|
code = pixel;
|
|
|
|
}
|
|
|
|
else
|
|
code = newCode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (code != -1)
|
|
{
|
|
PutCodeWord (code);
|
|
AddTable (code, 0);
|
|
}
|
|
|
|
PutCodeWord (kEndCode);
|
|
|
|
dCount = (fBitOffset + 7) >> 3;
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
#if qDNGUseLibJPEG
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void dng_error_exit (j_common_ptr cinfo)
|
|
{
|
|
|
|
// Output message.
|
|
|
|
(*cinfo->err->output_message) (cinfo);
|
|
|
|
// Convert to a dng_exception.
|
|
|
|
switch (cinfo->err->msg_code)
|
|
{
|
|
|
|
case JERR_OUT_OF_MEMORY:
|
|
{
|
|
ThrowMemoryFull ();
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
ThrowBadFormat ();
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void dng_output_message (j_common_ptr cinfo)
|
|
{
|
|
|
|
// Format message to string.
|
|
|
|
char buffer [JMSG_LENGTH_MAX];
|
|
|
|
(*cinfo->err->format_message) (cinfo, buffer);
|
|
|
|
// Report the libjpeg message as a warning.
|
|
|
|
ReportWarning ("libjpeg", buffer);
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
struct dng_jpeg_stream_dest
|
|
{
|
|
|
|
struct jpeg_destination_mgr pub;
|
|
|
|
dng_stream *fStream;
|
|
|
|
uint8 fBuffer [4096];
|
|
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void dng_init_destination (j_compress_ptr cinfo)
|
|
{
|
|
|
|
dng_jpeg_stream_dest *dest = (dng_jpeg_stream_dest *) cinfo->dest;
|
|
|
|
dest->pub.next_output_byte = dest->fBuffer;
|
|
dest->pub.free_in_buffer = sizeof (dest->fBuffer);
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static boolean dng_empty_output_buffer (j_compress_ptr cinfo)
|
|
{
|
|
|
|
dng_jpeg_stream_dest *dest = (dng_jpeg_stream_dest *) cinfo->dest;
|
|
|
|
dest->fStream->Put (dest->fBuffer, sizeof (dest->fBuffer));
|
|
|
|
dest->pub.next_output_byte = dest->fBuffer;
|
|
dest->pub.free_in_buffer = sizeof (dest->fBuffer);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void dng_term_destination (j_compress_ptr cinfo)
|
|
{
|
|
|
|
dng_jpeg_stream_dest *dest = (dng_jpeg_stream_dest *) cinfo->dest;
|
|
|
|
uint32 datacount = sizeof (dest->fBuffer) -
|
|
(uint32) dest->pub.free_in_buffer;
|
|
|
|
if (datacount)
|
|
{
|
|
dest->fStream->Put (dest->fBuffer, datacount);
|
|
}
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void jpeg_set_adobe_quality (struct jpeg_compress_struct *cinfo,
|
|
int32 quality)
|
|
{
|
|
|
|
// If out of range, map to default.
|
|
|
|
if (quality < 0 || quality > 12)
|
|
{
|
|
quality = 10;
|
|
}
|
|
|
|
// Adobe turns off chroma downsampling at high quality levels.
|
|
|
|
bool useChromaDownsampling = (quality <= 6);
|
|
|
|
// Approximate mapping from Adobe quality levels to LibJPEG levels.
|
|
|
|
const int kLibJPEGQuality [13] =
|
|
{
|
|
5, 11, 23, 34, 46, 63, 76, 77, 86, 90, 94, 97, 99
|
|
};
|
|
|
|
quality = kLibJPEGQuality [quality];
|
|
|
|
jpeg_set_quality (cinfo, quality, TRUE);
|
|
|
|
// LibJPEG defaults to always using chroma downsampling. Turn if off
|
|
// if we need it off to match Adobe.
|
|
|
|
if (!useChromaDownsampling)
|
|
{
|
|
|
|
cinfo->comp_info [0].h_samp_factor = 1;
|
|
cinfo->comp_info [0].h_samp_factor = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
#endif
|
|
|
|
/*****************************************************************************/
|
|
|
|
void dng_image_writer::WriteData (dng_host &host,
|
|
const dng_ifd &ifd,
|
|
dng_stream &stream,
|
|
dng_pixel_buffer &buffer,
|
|
AutoPtr<dng_memory_block> &compressedBuffer)
|
|
{
|
|
|
|
switch (ifd.fCompression)
|
|
{
|
|
|
|
case ccUncompressed:
|
|
{
|
|
|
|
// Special case support for when we save to 8-bits from
|
|
// 16-bit data.
|
|
|
|
if (ifd.fBitsPerSample [0] == 8 && buffer.fPixelType == ttShort)
|
|
{
|
|
|
|
uint32 count = buffer.fRowStep *
|
|
buffer.fArea.H ();
|
|
|
|
const uint16 *sPtr = (const uint16 *) buffer.fData;
|
|
|
|
for (uint32 j = 0; j < count; j++)
|
|
{
|
|
|
|
stream.Put_uint8 ((uint8) sPtr [j]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
|
|
// Swap bytes if required.
|
|
|
|
if (stream.SwapBytes ())
|
|
{
|
|
|
|
ByteSwapBuffer (host, buffer);
|
|
|
|
}
|
|
|
|
// Write the bytes.
|
|
|
|
stream.Put (buffer.fData, buffer.fRowStep *
|
|
buffer.fArea.H () *
|
|
buffer.fPixelSize);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case ccLZW:
|
|
case ccDeflate:
|
|
{
|
|
|
|
// Both these compression algorithms are byte based. The floating
|
|
// point predictor already does byte ordering, so don't ever swap
|
|
// when using it.
|
|
|
|
if (stream.SwapBytes () && ifd.fPredictor != cpFloatingPoint)
|
|
{
|
|
|
|
ByteSwapBuffer (host,
|
|
buffer);
|
|
|
|
}
|
|
|
|
// Run the compression algorithm.
|
|
|
|
uint32 sBytes = buffer.fRowStep *
|
|
buffer.fArea.H () *
|
|
buffer.fPixelSize;
|
|
|
|
uint8 *sBuffer = (uint8 *) buffer.fData;
|
|
|
|
uint32 dBytes = 0;
|
|
|
|
uint8 *dBuffer = compressedBuffer->Buffer_uint8 ();
|
|
|
|
if (ifd.fCompression == ccLZW)
|
|
{
|
|
|
|
dng_lzw_compressor lzwCompressor;
|
|
|
|
lzwCompressor.Compress (sBuffer,
|
|
dBuffer,
|
|
sBytes,
|
|
dBytes);
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
|
|
uLongf dCount = compressedBuffer->LogicalSize ();
|
|
|
|
int32 level = Z_DEFAULT_COMPRESSION;
|
|
|
|
if (ifd.fCompressionQuality >= Z_BEST_SPEED &&
|
|
ifd.fCompressionQuality <= Z_BEST_COMPRESSION)
|
|
{
|
|
|
|
level = ifd.fCompressionQuality;
|
|
|
|
}
|
|
|
|
int zResult = ::compress2 (dBuffer,
|
|
&dCount,
|
|
sBuffer,
|
|
sBytes,
|
|
level);
|
|
|
|
if (zResult != Z_OK)
|
|
{
|
|
|
|
ThrowMemoryFull ();
|
|
|
|
}
|
|
|
|
dBytes = (uint32) dCount;
|
|
|
|
}
|
|
|
|
if (dBytes > compressedBuffer->LogicalSize ())
|
|
{
|
|
|
|
DNG_REPORT ("Compression output buffer overflow");
|
|
|
|
ThrowProgramError ();
|
|
|
|
}
|
|
|
|
stream.Put (dBuffer, dBytes);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
case ccJPEG:
|
|
{
|
|
|
|
dng_pixel_buffer temp (buffer);
|
|
|
|
if (buffer.fPixelType == ttByte)
|
|
{
|
|
|
|
// The lossless JPEG encoder needs 16-bit data, so if we are
|
|
// are saving 8 bit data, we need to pad it out to 16-bits.
|
|
|
|
temp.fData = compressedBuffer->Buffer ();
|
|
|
|
temp.fPixelType = ttShort;
|
|
temp.fPixelSize = 2;
|
|
|
|
temp.CopyArea (buffer,
|
|
buffer.fArea,
|
|
buffer.fPlane,
|
|
buffer.fPlanes);
|
|
|
|
}
|
|
|
|
EncodeLosslessJPEG ((const uint16 *) temp.fData,
|
|
temp.fArea.H (),
|
|
temp.fArea.W (),
|
|
temp.fPlanes,
|
|
ifd.fBitsPerSample [0],
|
|
temp.fRowStep,
|
|
temp.fColStep,
|
|
stream);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
#if qDNGUseLibJPEG
|
|
|
|
case ccLossyJPEG:
|
|
{
|
|
|
|
struct jpeg_compress_struct cinfo;
|
|
|
|
// Setup the error manager.
|
|
|
|
struct jpeg_error_mgr jerr;
|
|
|
|
cinfo.err = jpeg_std_error (&jerr);
|
|
|
|
jerr.error_exit = dng_error_exit;
|
|
jerr.output_message = dng_output_message;
|
|
|
|
try
|
|
{
|
|
|
|
// Create the compression context.
|
|
|
|
jpeg_create_compress (&cinfo);
|
|
|
|
// Setup the destination manager to write to stream.
|
|
|
|
dng_jpeg_stream_dest dest;
|
|
|
|
dest.fStream = &stream;
|
|
|
|
dest.pub.init_destination = dng_init_destination;
|
|
dest.pub.empty_output_buffer = dng_empty_output_buffer;
|
|
dest.pub.term_destination = dng_term_destination;
|
|
|
|
cinfo.dest = &dest.pub;
|
|
|
|
// Setup basic image info.
|
|
|
|
cinfo.image_width = buffer.fArea.W ();
|
|
cinfo.image_height = buffer.fArea.H ();
|
|
cinfo.input_components = buffer.fPlanes;
|
|
|
|
switch (buffer.fPlanes)
|
|
{
|
|
|
|
case 1:
|
|
cinfo.in_color_space = JCS_GRAYSCALE;
|
|
break;
|
|
|
|
case 3:
|
|
cinfo.in_color_space = JCS_RGB;
|
|
break;
|
|
|
|
case 4:
|
|
cinfo.in_color_space = JCS_CMYK;
|
|
break;
|
|
|
|
default:
|
|
ThrowProgramError ();
|
|
|
|
}
|
|
|
|
// Setup the compression parameters.
|
|
|
|
jpeg_set_defaults (&cinfo);
|
|
|
|
jpeg_set_adobe_quality (&cinfo, ifd.fCompressionQuality);
|
|
|
|
// Write the JPEG header.
|
|
|
|
jpeg_start_compress (&cinfo, TRUE);
|
|
|
|
// Write the scanlines.
|
|
|
|
for (int32 row = buffer.fArea.t; row < buffer.fArea.b; row++)
|
|
{
|
|
|
|
uint8 *sampArray [1];
|
|
|
|
sampArray [0] = buffer.DirtyPixel_uint8 (row,
|
|
buffer.fArea.l,
|
|
0);
|
|
|
|
jpeg_write_scanlines (&cinfo, sampArray, 1);
|
|
|
|
}
|
|
|
|
// Cleanup.
|
|
|
|
jpeg_finish_compress (&cinfo);
|
|
|
|
jpeg_destroy_compress (&cinfo);
|
|
|
|
}
|
|
|
|
catch (...)
|
|
{
|
|
|
|
jpeg_destroy_compress (&cinfo);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
default:
|
|
{
|
|
|
|
ThrowProgramError ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
void dng_image_writer::EncodeJPEGPreview (dng_host &host,
|
|
const dng_image &image,
|
|
dng_jpeg_preview &preview,
|
|
int32 quality)
|
|
{
|
|
|
|
#if qDNGUseLibJPEG
|
|
|
|
dng_memory_stream stream (host.Allocator ());
|
|
|
|
struct jpeg_compress_struct cinfo;
|
|
|
|
// Setup the error manager.
|
|
|
|
struct jpeg_error_mgr jerr;
|
|
|
|
cinfo.err = jpeg_std_error (&jerr);
|
|
|
|
jerr.error_exit = dng_error_exit;
|
|
jerr.output_message = dng_output_message;
|
|
|
|
try
|
|
{
|
|
|
|
// Create the compression context.
|
|
|
|
jpeg_create_compress (&cinfo);
|
|
|
|
// Setup the destination manager to write to stream.
|
|
|
|
dng_jpeg_stream_dest dest;
|
|
|
|
dest.fStream = &stream;
|
|
|
|
dest.pub.init_destination = dng_init_destination;
|
|
dest.pub.empty_output_buffer = dng_empty_output_buffer;
|
|
dest.pub.term_destination = dng_term_destination;
|
|
|
|
cinfo.dest = &dest.pub;
|
|
|
|
// Setup basic image info.
|
|
|
|
cinfo.image_width = image.Bounds ().W ();
|
|
cinfo.image_height = image.Bounds ().H ();
|
|
cinfo.input_components = image.Planes ();
|
|
|
|
switch (image.Planes ())
|
|
{
|
|
|
|
case 1:
|
|
cinfo.in_color_space = JCS_GRAYSCALE;
|
|
break;
|
|
|
|
case 3:
|
|
cinfo.in_color_space = JCS_RGB;
|
|
break;
|
|
|
|
default:
|
|
ThrowProgramError ();
|
|
|
|
}
|
|
|
|
// Setup the compression parameters.
|
|
|
|
jpeg_set_defaults (&cinfo);
|
|
|
|
jpeg_set_adobe_quality (&cinfo, quality);
|
|
|
|
// Find some preview information based on the compression settings.
|
|
|
|
preview.fPreviewSize = image.Size ();
|
|
|
|
if (image.Planes () == 1)
|
|
{
|
|
|
|
preview.fPhotometricInterpretation = piBlackIsZero;
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
|
|
preview.fPhotometricInterpretation = piYCbCr;
|
|
|
|
preview.fYCbCrSubSampling.h = cinfo.comp_info [0].h_samp_factor;
|
|
preview.fYCbCrSubSampling.v = cinfo.comp_info [0].v_samp_factor;
|
|
|
|
}
|
|
|
|
// Write the JPEG header.
|
|
|
|
jpeg_start_compress (&cinfo, TRUE);
|
|
|
|
// Write the scanlines.
|
|
|
|
dng_pixel_buffer buffer (image.Bounds (), 0, image.Planes (), ttByte,
|
|
pcInterleaved, NULL);
|
|
|
|
AutoPtr<dng_memory_block> bufferData (host.Allocate (buffer.fRowStep));
|
|
|
|
buffer.fData = bufferData->Buffer ();
|
|
|
|
for (uint32 row = 0; row < cinfo.image_height; row++)
|
|
{
|
|
|
|
buffer.fArea.t = row;
|
|
buffer.fArea.b = row + 1;
|
|
|
|
image.Get (buffer);
|
|
|
|
uint8 *sampArray [1];
|
|
|
|
sampArray [0] = buffer.DirtyPixel_uint8 (row,
|
|
buffer.fArea.l,
|
|
0);
|
|
|
|
jpeg_write_scanlines (&cinfo, sampArray, 1);
|
|
|
|
}
|
|
|
|
// Cleanup.
|
|
|
|
jpeg_finish_compress (&cinfo);
|
|
|
|
jpeg_destroy_compress (&cinfo);
|
|
|
|
}
|
|
|
|
catch (...)
|
|
{
|
|
|
|
jpeg_destroy_compress (&cinfo);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
preview.fCompressedData.Reset (stream.AsMemoryBlock (host.Allocator ()));
|
|
|
|
#else
|
|
|
|
(void) host;
|
|
(void) image;
|
|
(void) preview;
|
|
(void) quality;
|
|
|
|
ThrowProgramError ("No JPEG encoder");
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void dng_image_writer::WriteTile (dng_host &host,
|
|
const dng_ifd &ifd,
|
|
dng_stream &stream,
|
|
const dng_image &image,
|
|
const dng_rect &tileArea,
|
|
uint32 fakeChannels,
|
|
AutoPtr<dng_memory_block> &compressedBuffer,
|
|
AutoPtr<dng_memory_block> &uncompressedBuffer,
|
|
AutoPtr<dng_memory_block> &subTileBlockBuffer,
|
|
AutoPtr<dng_memory_block> &tempBuffer)
|
|
{
|
|
|
|
// Create pixel buffer to hold uncompressed tile.
|
|
|
|
dng_pixel_buffer buffer (tileArea, 0, ifd.fSamplesPerPixel,
|
|
image.PixelType(), pcInterleaved, uncompressedBuffer->Buffer());
|
|
|
|
// Get the uncompressed data.
|
|
|
|
image.Get (buffer, dng_image::edge_zero);
|
|
|
|
// Deal with sub-tile blocks.
|
|
|
|
if (ifd.fSubTileBlockRows > 1)
|
|
{
|
|
|
|
ReorderSubTileBlocks (ifd,
|
|
buffer,
|
|
uncompressedBuffer,
|
|
subTileBlockBuffer);
|
|
|
|
}
|
|
|
|
// Floating point depth conversion.
|
|
|
|
if (ifd.fSampleFormat [0] == sfFloatingPoint)
|
|
{
|
|
|
|
if (ifd.fBitsPerSample [0] == 16)
|
|
{
|
|
|
|
uint32 *srcPtr = (uint32 *) buffer.fData;
|
|
uint16 *dstPtr = (uint16 *) buffer.fData;
|
|
|
|
uint32 pixels = tileArea.W () * tileArea.H () * buffer.fPlanes;
|
|
|
|
for (uint32 j = 0; j < pixels; j++)
|
|
{
|
|
|
|
dstPtr [j] = DNG_FloatToHalf (srcPtr [j]);
|
|
|
|
}
|
|
|
|
buffer.fPixelSize = 2;
|
|
|
|
}
|
|
|
|
if (ifd.fBitsPerSample [0] == 24)
|
|
{
|
|
|
|
uint32 *srcPtr = (uint32 *) buffer.fData;
|
|
uint8 *dstPtr = (uint8 *) buffer.fData;
|
|
|
|
uint32 pixels = tileArea.W () * tileArea.H () * buffer.fPlanes;
|
|
|
|
if (stream.BigEndian () || ifd.fPredictor == cpFloatingPoint ||
|
|
ifd.fPredictor == cpFloatingPointX2 ||
|
|
ifd.fPredictor == cpFloatingPointX4)
|
|
{
|
|
|
|
for (uint32 j = 0; j < pixels; j++)
|
|
{
|
|
|
|
DNG_FloatToFP24 (srcPtr [j], dstPtr);
|
|
|
|
dstPtr += 3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
|
|
for (uint32 j = 0; j < pixels; j++)
|
|
{
|
|
|
|
uint8 output [3];
|
|
|
|
DNG_FloatToFP24 (srcPtr [j], output);
|
|
|
|
dstPtr [0] = output [2];
|
|
dstPtr [1] = output [1];
|
|
dstPtr [2] = output [0];
|
|
|
|
dstPtr += 3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
buffer.fPixelSize = 3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Run predictor.
|
|
|
|
EncodePredictor (host,
|
|
ifd,
|
|
buffer,
|
|
tempBuffer);
|
|
|
|
// Adjust pixel buffer for fake channels.
|
|
|
|
if (fakeChannels > 1)
|
|
{
|
|
|
|
buffer.fPlanes *= fakeChannels;
|
|
buffer.fColStep *= fakeChannels;
|
|
|
|
buffer.fArea.r = buffer.fArea.l + (buffer.fArea.W () / fakeChannels);
|
|
|
|
}
|
|
|
|
// Compress (if required) and write out the data.
|
|
|
|
WriteData (host,
|
|
ifd,
|
|
stream,
|
|
buffer,
|
|
compressedBuffer);
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
class dng_write_tiles_task : public dng_area_task
|
|
{
|
|
|
|
private:
|
|
|
|
dng_image_writer &fImageWriter;
|
|
|
|
dng_host &fHost;
|
|
|
|
const dng_ifd &fIFD;
|
|
|
|
dng_basic_tag_set &fBasic;
|
|
|
|
dng_stream &fStream;
|
|
|
|
const dng_image &fImage;
|
|
|
|
uint32 fFakeChannels;
|
|
|
|
uint32 fTilesDown;
|
|
|
|
uint32 fTilesAcross;
|
|
|
|
uint32 fCompressedSize;
|
|
|
|
uint32 fUncompressedSize;
|
|
|
|
dng_mutex fMutex1;
|
|
|
|
uint32 fNextTileIndex;
|
|
|
|
dng_mutex fMutex2;
|
|
|
|
dng_condition fCondition;
|
|
|
|
bool fTaskFailed;
|
|
|
|
uint32 fWriteTileIndex;
|
|
|
|
public:
|
|
|
|
dng_write_tiles_task (dng_image_writer &imageWriter,
|
|
dng_host &host,
|
|
const dng_ifd &ifd,
|
|
dng_basic_tag_set &basic,
|
|
dng_stream &stream,
|
|
const dng_image &image,
|
|
uint32 fakeChannels,
|
|
uint32 tilesDown,
|
|
uint32 tilesAcross,
|
|
uint32 compressedSize,
|
|
uint32 uncompressedSize)
|
|
|
|
: fImageWriter (imageWriter)
|
|
, fHost (host)
|
|
, fIFD (ifd)
|
|
, fBasic (basic)
|
|
, fStream (stream)
|
|
, fImage (image)
|
|
, fFakeChannels (fakeChannels)
|
|
, fTilesDown (tilesDown)
|
|
, fTilesAcross (tilesAcross)
|
|
, fCompressedSize (compressedSize)
|
|
, fUncompressedSize (uncompressedSize)
|
|
, fMutex1 ("dng_write_tiles_task_1")
|
|
, fNextTileIndex (0)
|
|
, fMutex2 ("dng_write_tiles_task_2")
|
|
, fCondition ()
|
|
, fTaskFailed (false)
|
|
, fWriteTileIndex (0)
|
|
|
|
{
|
|
|
|
fMinTaskArea = 16 * 16;
|
|
fUnitCell = dng_point (16, 16);
|
|
fMaxTileSize = dng_point (16, 16);
|
|
|
|
}
|
|
|
|
void Process (uint32 /* threadIndex */,
|
|
const dng_rect & /* tile */,
|
|
dng_abort_sniffer *sniffer)
|
|
{
|
|
|
|
try
|
|
{
|
|
|
|
AutoPtr<dng_memory_block> compressedBuffer;
|
|
AutoPtr<dng_memory_block> uncompressedBuffer;
|
|
AutoPtr<dng_memory_block> subTileBlockBuffer;
|
|
AutoPtr<dng_memory_block> tempBuffer;
|
|
|
|
if (fCompressedSize)
|
|
{
|
|
compressedBuffer.Reset (fHost.Allocate (fCompressedSize));
|
|
}
|
|
|
|
if (fUncompressedSize)
|
|
{
|
|
uncompressedBuffer.Reset (fHost.Allocate (fUncompressedSize));
|
|
}
|
|
|
|
if (fIFD.fSubTileBlockRows > 1 && fUncompressedSize)
|
|
{
|
|
subTileBlockBuffer.Reset (fHost.Allocate (fUncompressedSize));
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
|
|
// Find tile index to compress.
|
|
|
|
uint32 tileIndex;
|
|
|
|
{
|
|
|
|
dng_lock_mutex lock (&fMutex1);
|
|
|
|
if (fNextTileIndex == fTilesDown * fTilesAcross)
|
|
{
|
|
return;
|
|
}
|
|
|
|
tileIndex = fNextTileIndex++;
|
|
|
|
}
|
|
|
|
dng_abort_sniffer::SniffForAbort (sniffer);
|
|
|
|
// Compress tile.
|
|
|
|
uint32 rowIndex = tileIndex / fTilesAcross;
|
|
|
|
uint32 colIndex = tileIndex - rowIndex * fTilesAcross;
|
|
|
|
dng_rect tileArea = fIFD.TileArea (rowIndex, colIndex);
|
|
|
|
dng_memory_stream tileStream (fHost.Allocator ());
|
|
|
|
tileStream.SetLittleEndian (fStream.LittleEndian ());
|
|
|
|
dng_host host (&fHost.Allocator (),
|
|
sniffer);
|
|
|
|
fImageWriter.WriteTile (host,
|
|
fIFD,
|
|
tileStream,
|
|
fImage,
|
|
tileArea,
|
|
fFakeChannels,
|
|
compressedBuffer,
|
|
uncompressedBuffer,
|
|
subTileBlockBuffer,
|
|
tempBuffer);
|
|
|
|
tileStream.Flush ();
|
|
|
|
uint32 tileByteCount = (uint32) tileStream.Length ();
|
|
|
|
tileStream.SetReadPosition (0);
|
|
|
|
// Wait until it is our turn to write tile.
|
|
|
|
{
|
|
|
|
dng_lock_mutex lock (&fMutex2);
|
|
|
|
while (!fTaskFailed &&
|
|
fWriteTileIndex != tileIndex)
|
|
{
|
|
|
|
fCondition.Wait (fMutex2);
|
|
|
|
}
|
|
|
|
// If the task failed in another thread, that thread already threw an exception.
|
|
|
|
if (fTaskFailed)
|
|
return;
|
|
|
|
}
|
|
|
|
dng_abort_sniffer::SniffForAbort (sniffer);
|
|
|
|
// Remember this offset.
|
|
|
|
uint32 tileOffset = (uint32) fStream.Position ();
|
|
|
|
fBasic.SetTileOffset (tileIndex, tileOffset);
|
|
|
|
// Copy tile stream for tile into main stream.
|
|
|
|
tileStream.CopyToStream (fStream, tileByteCount);
|
|
|
|
// Update tile count.
|
|
|
|
fBasic.SetTileByteCount (tileIndex, tileByteCount);
|
|
|
|
// Keep the tiles on even byte offsets.
|
|
|
|
if (tileByteCount & 1)
|
|
{
|
|
fStream.Put_uint8 (0);
|
|
}
|
|
|
|
// Let other threads know it is safe to write to stream.
|
|
|
|
{
|
|
|
|
dng_lock_mutex lock (&fMutex2);
|
|
|
|
// If the task failed in another thread, that thread already threw an exception.
|
|
|
|
if (fTaskFailed)
|
|
return;
|
|
|
|
fWriteTileIndex++;
|
|
|
|
fCondition.Broadcast ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (...)
|
|
{
|
|
|
|
// If first to fail, wake up any threads waiting on condition.
|
|
|
|
bool needBroadcast = false;
|
|
|
|
{
|
|
|
|
dng_lock_mutex lock (&fMutex2);
|
|
|
|
needBroadcast = !fTaskFailed;
|
|
fTaskFailed = true;
|
|
|
|
}
|
|
|
|
if (needBroadcast)
|
|
fCondition.Broadcast ();
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
private:
|
|
|
|
// Hidden copy constructor and assignment operator.
|
|
|
|
dng_write_tiles_task (const dng_write_tiles_task &);
|
|
|
|
dng_write_tiles_task & operator= (const dng_write_tiles_task &);
|
|
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
|
|
void dng_image_writer::WriteImage (dng_host &host,
|
|
const dng_ifd &ifd,
|
|
dng_basic_tag_set &basic,
|
|
dng_stream &stream,
|
|
const dng_image &image,
|
|
uint32 fakeChannels)
|
|
{
|
|
|
|
// Deal with row interleaved images.
|
|
|
|
if (ifd.fRowInterleaveFactor > 1 &&
|
|
ifd.fRowInterleaveFactor < ifd.fImageLength)
|
|
{
|
|
|
|
dng_ifd tempIFD (ifd);
|
|
|
|
tempIFD.fRowInterleaveFactor = 1;
|
|
|
|
dng_row_interleaved_image tempImage (*((dng_image *) &image),
|
|
ifd.fRowInterleaveFactor);
|
|
|
|
WriteImage (host,
|
|
tempIFD,
|
|
basic,
|
|
stream,
|
|
tempImage,
|
|
fakeChannels);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Compute basic information.
|
|
|
|
uint32 bytesPerSample = TagTypeSize (image.PixelType ());
|
|
|
|
uint32 bytesPerPixel = SafeUint32Mult (ifd.fSamplesPerPixel,
|
|
bytesPerSample);
|
|
|
|
uint32 tileRowBytes = SafeUint32Mult (ifd.fTileWidth, bytesPerPixel);
|
|
|
|
// If we can compute the number of bytes needed to store the
|
|
// data, we can split the write for each tile into sub-tiles.
|
|
|
|
uint32 subTileLength = ifd.fTileLength;
|
|
|
|
if (ifd.TileByteCount (ifd.TileArea (0, 0)) != 0)
|
|
{
|
|
|
|
subTileLength = Pin_uint32 (ifd.fSubTileBlockRows,
|
|
kImageBufferSize / tileRowBytes,
|
|
ifd.fTileLength);
|
|
|
|
// Don't split sub-tiles across subTileBlocks.
|
|
|
|
subTileLength = subTileLength / ifd.fSubTileBlockRows
|
|
* ifd.fSubTileBlockRows;
|
|
|
|
}
|
|
|
|
// Find size of uncompressed buffer.
|
|
|
|
uint32 uncompressedSize = SafeUint32Mult(subTileLength, tileRowBytes);
|
|
|
|
// Find size of compressed buffer, if required.
|
|
|
|
uint32 compressedSize = CompressedBufferSize (ifd, uncompressedSize);
|
|
|
|
// See if we can do this write using multiple threads.
|
|
|
|
uint32 tilesAcross = ifd.TilesAcross ();
|
|
uint32 tilesDown = ifd.TilesDown ();
|
|
|
|
bool useMultipleThreads = (tilesDown * tilesAcross >= 2) &&
|
|
(host.PerformAreaTaskThreads () > 1) &&
|
|
(subTileLength == ifd.fTileLength) &&
|
|
(ifd.fCompression != ccUncompressed);
|
|
|
|
|
|
#if qImagecore
|
|
useMultipleThreads = false;
|
|
#endif
|
|
|
|
if (useMultipleThreads)
|
|
{
|
|
|
|
uint32 threadCount = Min_uint32 (tilesDown * tilesAcross,
|
|
host.PerformAreaTaskThreads ());
|
|
|
|
dng_write_tiles_task task (*this,
|
|
host,
|
|
ifd,
|
|
basic,
|
|
stream,
|
|
image,
|
|
fakeChannels,
|
|
tilesDown,
|
|
tilesAcross,
|
|
compressedSize,
|
|
uncompressedSize);
|
|
|
|
host.PerformAreaTask (task,
|
|
dng_rect (0, 0, 16, 16 * threadCount));
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
|
|
AutoPtr<dng_memory_block> compressedBuffer;
|
|
AutoPtr<dng_memory_block> uncompressedBuffer;
|
|
AutoPtr<dng_memory_block> subTileBlockBuffer;
|
|
AutoPtr<dng_memory_block> tempBuffer;
|
|
|
|
if (compressedSize)
|
|
{
|
|
compressedBuffer.Reset (host.Allocate (compressedSize));
|
|
}
|
|
|
|
if (uncompressedSize)
|
|
{
|
|
uncompressedBuffer.Reset (host.Allocate (uncompressedSize));
|
|
}
|
|
|
|
if (ifd.fSubTileBlockRows > 1 && uncompressedSize)
|
|
{
|
|
subTileBlockBuffer.Reset (host.Allocate (uncompressedSize));
|
|
}
|
|
|
|
// Write out each tile.
|
|
|
|
uint32 tileIndex = 0;
|
|
|
|
for (uint32 rowIndex = 0; rowIndex < tilesDown; rowIndex++)
|
|
{
|
|
|
|
for (uint32 colIndex = 0; colIndex < tilesAcross; colIndex++)
|
|
{
|
|
|
|
// Remember this offset.
|
|
|
|
uint32 tileOffset = (uint32) stream.Position ();
|
|
|
|
basic.SetTileOffset (tileIndex, tileOffset);
|
|
|
|
// Split tile into sub-tiles if possible.
|
|
|
|
dng_rect tileArea = ifd.TileArea (rowIndex, colIndex);
|
|
|
|
uint32 subTileCount = (tileArea.H () + subTileLength - 1) /
|
|
subTileLength;
|
|
|
|
for (uint32 subIndex = 0; subIndex < subTileCount; subIndex++)
|
|
{
|
|
|
|
host.SniffForAbort ();
|
|
|
|
dng_rect subArea (tileArea);
|
|
|
|
subArea.t = tileArea.t + subIndex * subTileLength;
|
|
|
|
subArea.b = Min_int32 (subArea.t + subTileLength,
|
|
tileArea.b);
|
|
|
|
// Write the sub-tile.
|
|
|
|
WriteTile (host,
|
|
ifd,
|
|
stream,
|
|
image,
|
|
subArea,
|
|
fakeChannels,
|
|
compressedBuffer,
|
|
uncompressedBuffer,
|
|
subTileBlockBuffer,
|
|
tempBuffer);
|
|
|
|
}
|
|
|
|
// Update tile count.
|
|
|
|
uint32 tileByteCount = (uint32) stream.Position () - tileOffset;
|
|
|
|
basic.SetTileByteCount (tileIndex, tileByteCount);
|
|
|
|
tileIndex++;
|
|
|
|
// Keep the tiles on even byte offsets.
|
|
|
|
if (tileByteCount & 1)
|
|
{
|
|
stream.Put_uint8 (0);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
#if qDNGUseXMP
|
|
|
|
static void CopyString (const dng_xmp &oldXMP,
|
|
dng_xmp &newXMP,
|
|
const char *ns,
|
|
const char *path,
|
|
dng_string *exif = NULL)
|
|
{
|
|
|
|
dng_string s;
|
|
|
|
if (oldXMP.GetString (ns, path, s))
|
|
{
|
|
|
|
if (s.NotEmpty ())
|
|
{
|
|
|
|
newXMP.SetString (ns, path, s);
|
|
|
|
if (exif)
|
|
{
|
|
|
|
*exif = s;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void CopyStringList (const dng_xmp &oldXMP,
|
|
dng_xmp &newXMP,
|
|
const char *ns,
|
|
const char *path,
|
|
bool isBag)
|
|
{
|
|
|
|
dng_string_list list;
|
|
|
|
if (oldXMP.GetStringList (ns, path, list))
|
|
{
|
|
|
|
if (list.Count ())
|
|
{
|
|
|
|
newXMP.SetStringList (ns, path, list, isBag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void CopyAltLangDefault (const dng_xmp &oldXMP,
|
|
dng_xmp &newXMP,
|
|
const char *ns,
|
|
const char *path,
|
|
dng_string *exif = NULL)
|
|
{
|
|
|
|
dng_string s;
|
|
|
|
if (oldXMP.GetAltLangDefault (ns, path, s))
|
|
{
|
|
|
|
if (s.NotEmpty ())
|
|
{
|
|
|
|
newXMP.SetAltLangDefault (ns, path, s);
|
|
|
|
if (exif)
|
|
{
|
|
|
|
*exif = s;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void CopyStructField (const dng_xmp &oldXMP,
|
|
dng_xmp &newXMP,
|
|
const char *ns,
|
|
const char *path,
|
|
const char *field)
|
|
{
|
|
|
|
dng_string s;
|
|
|
|
if (oldXMP.GetStructField (ns, path, ns, field, s))
|
|
{
|
|
|
|
if (s.NotEmpty ())
|
|
{
|
|
|
|
newXMP.SetStructField (ns, path, ns, field, s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void CopyBoolean (const dng_xmp &oldXMP,
|
|
dng_xmp &newXMP,
|
|
const char *ns,
|
|
const char *path)
|
|
{
|
|
|
|
bool b;
|
|
|
|
if (oldXMP.GetBoolean (ns, path, b))
|
|
{
|
|
|
|
newXMP.SetBoolean (ns, path, b);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
/*****************************************************************************/
|
|
|
|
void dng_image_writer::CleanUpMetadata (dng_host &host,
|
|
dng_metadata &metadata,
|
|
dng_metadata_subset metadataSubset,
|
|
const char *dstMIMI,
|
|
const char *software)
|
|
{
|
|
|
|
#if qDNGUseXMP
|
|
|
|
if (metadata.GetXMP () && metadata.GetExif ())
|
|
{
|
|
|
|
dng_xmp &newXMP (*metadata.GetXMP ());
|
|
dng_exif &newEXIF (*metadata.GetExif ());
|
|
|
|
// Update software tag.
|
|
|
|
if (software)
|
|
{
|
|
|
|
newEXIF.fSoftware.Set (software);
|
|
|
|
newXMP.Set (XMP_NS_XAP,
|
|
"CreatorTool",
|
|
software);
|
|
|
|
}
|
|
|
|
#if qDNGXMPDocOps
|
|
|
|
newXMP.DocOpsPrepareForSave (metadata.SourceMIMI ().Get (),
|
|
dstMIMI);
|
|
|
|
#else
|
|
|
|
metadata.UpdateDateTimeToNow ();
|
|
|
|
#endif
|
|
|
|
// Update EXIF version to at least 2.3 so all the exif tags
|
|
// can be written.
|
|
|
|
if (newEXIF.fExifVersion < DNG_CHAR4 ('0','2','3','0'))
|
|
{
|
|
|
|
newEXIF.fExifVersion = DNG_CHAR4 ('0','2','3','0');
|
|
|
|
newXMP.Set (XMP_NS_EXIF, "ExifVersion", "0230");
|
|
|
|
}
|
|
|
|
// Resync EXIF, remove EXIF tags from XMP.
|
|
|
|
newXMP.SyncExif (newEXIF,
|
|
metadata.GetOriginalExif (),
|
|
false,
|
|
true);
|
|
|
|
// Deal with ImageIngesterPro bug. This program is adding lots of
|
|
// empty metadata strings into the XMP, which is screwing up Adobe CS4.
|
|
// We are saving a new file, so this is a chance to clean up this mess.
|
|
|
|
newXMP.RemoveEmptyStringsAndArrays (XMP_NS_DC);
|
|
newXMP.RemoveEmptyStringsAndArrays (XMP_NS_XAP);
|
|
newXMP.RemoveEmptyStringsAndArrays (XMP_NS_PHOTOSHOP);
|
|
newXMP.RemoveEmptyStringsAndArrays (XMP_NS_IPTC);
|
|
newXMP.RemoveEmptyStringsAndArrays (XMP_NS_XAP_RIGHTS);
|
|
newXMP.RemoveEmptyStringsAndArrays ("http://ns.iview-multimedia.com/mediapro/1.0/");
|
|
|
|
// Process metadata subset.
|
|
|
|
if (metadataSubset == kMetadataSubset_CopyrightOnly ||
|
|
metadataSubset == kMetadataSubset_CopyrightAndContact)
|
|
{
|
|
|
|
dng_xmp oldXMP (newXMP );
|
|
dng_exif oldEXIF (newEXIF);
|
|
|
|
// For these options, we start from nothing, and only fill in the
|
|
// fields that we absolutely need.
|
|
|
|
newXMP.RemoveProperties (NULL);
|
|
|
|
newEXIF.SetEmpty ();
|
|
|
|
metadata.ClearMakerNote ();
|
|
|
|
// Move copyright related fields over.
|
|
|
|
CopyAltLangDefault (oldXMP,
|
|
newXMP,
|
|
XMP_NS_DC,
|
|
"rights",
|
|
&newEXIF.fCopyright);
|
|
|
|
CopyAltLangDefault (oldXMP,
|
|
newXMP,
|
|
XMP_NS_XAP_RIGHTS,
|
|
"UsageTerms");
|
|
|
|
CopyString (oldXMP,
|
|
newXMP,
|
|
XMP_NS_XAP_RIGHTS,
|
|
"WebStatement");
|
|
|
|
CopyBoolean (oldXMP,
|
|
newXMP,
|
|
XMP_NS_XAP_RIGHTS,
|
|
"Marked");
|
|
|
|
#if qDNGXMPDocOps
|
|
|
|
// Include basic DocOps fields, but not the full history.
|
|
|
|
CopyString (oldXMP,
|
|
newXMP,
|
|
XMP_NS_MM,
|
|
"OriginalDocumentID");
|
|
|
|
CopyString (oldXMP,
|
|
newXMP,
|
|
XMP_NS_MM,
|
|
"DocumentID");
|
|
|
|
CopyString (oldXMP,
|
|
newXMP,
|
|
XMP_NS_MM,
|
|
"InstanceID");
|
|
|
|
CopyString (oldXMP,
|
|
newXMP,
|
|
XMP_NS_XAP,
|
|
"MetadataDate");
|
|
|
|
#endif
|
|
|
|
// Copyright and Contact adds the contact info fields.
|
|
|
|
if (metadataSubset == kMetadataSubset_CopyrightAndContact)
|
|
{
|
|
|
|
// Note: Save for Web is not including the dc:creator list, but it
|
|
// is part of the IPTC contract info metadata panel, so I
|
|
// think it should be copied as part of the contact info.
|
|
|
|
CopyStringList (oldXMP,
|
|
newXMP,
|
|
XMP_NS_DC,
|
|
"creator",
|
|
false);
|
|
|
|
// The first string dc:creator list is mirrored to the
|
|
// the exif artist tag, so copy that also.
|
|
|
|
newEXIF.fArtist = oldEXIF.fArtist;
|
|
|
|
// Copy other contact fields.
|
|
|
|
CopyString (oldXMP,
|
|
newXMP,
|
|
XMP_NS_PHOTOSHOP,
|
|
"AuthorsPosition");
|
|
|
|
CopyStructField (oldXMP,
|
|
newXMP,
|
|
XMP_NS_IPTC,
|
|
"CreatorContactInfo",
|
|
"CiEmailWork");
|
|
|
|
CopyStructField (oldXMP,
|
|
newXMP,
|
|
XMP_NS_IPTC,
|
|
"CreatorContactInfo",
|
|
"CiAdrExtadr");
|
|
|
|
CopyStructField (oldXMP,
|
|
newXMP,
|
|
XMP_NS_IPTC,
|
|
"CreatorContactInfo",
|
|
"CiAdrCity");
|
|
|
|
CopyStructField (oldXMP,
|
|
newXMP,
|
|
XMP_NS_IPTC,
|
|
"CreatorContactInfo",
|
|
"CiAdrRegion");
|
|
|
|
CopyStructField (oldXMP,
|
|
newXMP,
|
|
XMP_NS_IPTC,
|
|
"CreatorContactInfo",
|
|
"CiAdrPcode");
|
|
|
|
CopyStructField (oldXMP,
|
|
newXMP,
|
|
XMP_NS_IPTC,
|
|
"CreatorContactInfo",
|
|
"CiAdrCtry");
|
|
|
|
CopyStructField (oldXMP,
|
|
newXMP,
|
|
XMP_NS_IPTC,
|
|
"CreatorContactInfo",
|
|
"CiTelWork");
|
|
|
|
CopyStructField (oldXMP,
|
|
newXMP,
|
|
XMP_NS_IPTC,
|
|
"CreatorContactInfo",
|
|
"CiUrlWork");
|
|
|
|
CopyAltLangDefault (oldXMP,
|
|
newXMP,
|
|
XMP_NS_DC,
|
|
"title");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (metadataSubset == kMetadataSubset_AllExceptCameraInfo ||
|
|
metadataSubset == kMetadataSubset_AllExceptCameraAndLocation ||
|
|
metadataSubset == kMetadataSubset_AllExceptLocationInfo)
|
|
{
|
|
|
|
dng_xmp oldXMP (newXMP );
|
|
dng_exif oldEXIF (newEXIF);
|
|
|
|
if (metadataSubset == kMetadataSubset_AllExceptCameraInfo ||
|
|
metadataSubset == kMetadataSubset_AllExceptCameraAndLocation)
|
|
{
|
|
|
|
// This removes most of the EXIF info, so just copy the fields
|
|
// we are not deleting.
|
|
|
|
newEXIF.SetEmpty ();
|
|
|
|
newEXIF.fImageDescription = oldEXIF.fImageDescription; // Note: Differs from SFW
|
|
newEXIF.fSoftware = oldEXIF.fSoftware;
|
|
newEXIF.fArtist = oldEXIF.fArtist;
|
|
newEXIF.fCopyright = oldEXIF.fCopyright;
|
|
newEXIF.fCopyright2 = oldEXIF.fCopyright2;
|
|
newEXIF.fDateTime = oldEXIF.fDateTime;
|
|
newEXIF.fDateTimeOriginal = oldEXIF.fDateTimeOriginal;
|
|
newEXIF.fDateTimeDigitized = oldEXIF.fDateTimeDigitized;
|
|
newEXIF.fExifVersion = oldEXIF.fExifVersion;
|
|
newEXIF.fImageUniqueID = oldEXIF.fImageUniqueID;
|
|
|
|
newEXIF.CopyGPSFrom (oldEXIF);
|
|
|
|
// Remove exif info from XMP.
|
|
|
|
newXMP.RemoveProperties (XMP_NS_EXIF);
|
|
newXMP.RemoveProperties (XMP_NS_AUX);
|
|
|
|
// Remove Camera Raw info
|
|
|
|
newXMP.RemoveProperties (XMP_NS_CRS);
|
|
newXMP.RemoveProperties (XMP_NS_CRSS);
|
|
newXMP.RemoveProperties (XMP_NS_CRX);
|
|
|
|
// Remove DocOps history, since it contains the original
|
|
// camera format.
|
|
|
|
newXMP.Remove (XMP_NS_MM, "History");
|
|
|
|
// MakerNote contains camera info.
|
|
|
|
metadata.ClearMakerNote ();
|
|
|
|
}
|
|
|
|
if (metadataSubset == kMetadataSubset_AllExceptLocationInfo ||
|
|
metadataSubset == kMetadataSubset_AllExceptCameraAndLocation)
|
|
{
|
|
|
|
// Remove GPS fields.
|
|
|
|
dng_exif blankExif;
|
|
|
|
newEXIF.CopyGPSFrom (blankExif);
|
|
|
|
// Remove MakerNote just in case, because we don't know
|
|
// all of what is in it.
|
|
|
|
metadata.ClearMakerNote ();
|
|
|
|
// Remove XMP & IPTC location fields.
|
|
|
|
newXMP.Remove (XMP_NS_PHOTOSHOP, "City");
|
|
newXMP.Remove (XMP_NS_PHOTOSHOP, "State");
|
|
newXMP.Remove (XMP_NS_PHOTOSHOP, "Country");
|
|
newXMP.Remove (XMP_NS_IPTC, "Location");
|
|
newXMP.Remove (XMP_NS_IPTC, "CountryCode");
|
|
newXMP.Remove (XMP_NS_IPTC_EXT, "LocationCreated");
|
|
newXMP.Remove (XMP_NS_IPTC_EXT, "LocationShown");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Rebuild the legacy IPTC block, if needed.
|
|
|
|
bool isTIFF = (strcmp (dstMIMI, "image/tiff") == 0);
|
|
bool isDNG = (strcmp (dstMIMI, "image/dng" ) == 0);
|
|
|
|
if (!isDNG)
|
|
{
|
|
|
|
metadata.RebuildIPTC (host.Allocator (),
|
|
isTIFF);
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
|
|
metadata.ClearIPTC ();
|
|
|
|
}
|
|
|
|
// Clear format related XMP.
|
|
|
|
newXMP.ClearOrientation ();
|
|
|
|
newXMP.ClearImageInfo ();
|
|
|
|
newXMP.RemoveProperties (XMP_NS_DNG);
|
|
|
|
// All the formats we care about already keep the IPTC digest
|
|
// elsewhere, do we don't need to write it to the XMP.
|
|
|
|
newXMP.ClearIPTCDigest ();
|
|
|
|
// Make sure that sidecar specific tags never get written to files.
|
|
|
|
newXMP.Remove (XMP_NS_PHOTOSHOP, "SidecarForExtension");
|
|
newXMP.Remove (XMP_NS_PHOTOSHOP, "EmbeddedXMPDigest");
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void dng_image_writer::WriteTIFF (dng_host &host,
|
|
dng_stream &stream,
|
|
const dng_image &image,
|
|
uint32 photometricInterpretation,
|
|
uint32 compression,
|
|
dng_negative *negative,
|
|
const dng_color_space *space,
|
|
const dng_resolution *resolution,
|
|
const dng_jpeg_preview *thumbnail,
|
|
const dng_memory_block *imageResources,
|
|
dng_metadata_subset metadataSubset)
|
|
{
|
|
|
|
WriteTIFF (host,
|
|
stream,
|
|
image,
|
|
photometricInterpretation,
|
|
compression,
|
|
negative ? &(negative->Metadata ()) : NULL,
|
|
space,
|
|
resolution,
|
|
thumbnail,
|
|
imageResources,
|
|
metadataSubset);
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void dng_image_writer::WriteTIFF (dng_host &host,
|
|
dng_stream &stream,
|
|
const dng_image &image,
|
|
uint32 photometricInterpretation,
|
|
uint32 compression,
|
|
const dng_metadata *metadata,
|
|
const dng_color_space *space,
|
|
const dng_resolution *resolution,
|
|
const dng_jpeg_preview *thumbnail,
|
|
const dng_memory_block *imageResources,
|
|
dng_metadata_subset metadataSubset)
|
|
{
|
|
|
|
const void *profileData = NULL;
|
|
uint32 profileSize = 0;
|
|
|
|
const uint8 *data = NULL;
|
|
uint32 size = 0;
|
|
|
|
if (space && space->ICCProfile (size, data))
|
|
{
|
|
|
|
profileData = data;
|
|
profileSize = size;
|
|
|
|
}
|
|
|
|
WriteTIFFWithProfile (host,
|
|
stream,
|
|
image,
|
|
photometricInterpretation,
|
|
compression,
|
|
metadata,
|
|
profileData,
|
|
profileSize,
|
|
resolution,
|
|
thumbnail,
|
|
imageResources,
|
|
metadataSubset);
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void dng_image_writer::WriteTIFFWithProfile (dng_host &host,
|
|
dng_stream &stream,
|
|
const dng_image &image,
|
|
uint32 photometricInterpretation,
|
|
uint32 compression,
|
|
dng_negative *negative,
|
|
const void *profileData,
|
|
uint32 profileSize,
|
|
const dng_resolution *resolution,
|
|
const dng_jpeg_preview *thumbnail,
|
|
const dng_memory_block *imageResources,
|
|
dng_metadata_subset metadataSubset)
|
|
{
|
|
|
|
WriteTIFFWithProfile (host,
|
|
stream,
|
|
image,
|
|
photometricInterpretation,
|
|
compression,
|
|
negative ? &(negative->Metadata ()) : NULL,
|
|
profileData,
|
|
profileSize,
|
|
resolution,
|
|
thumbnail,
|
|
imageResources,
|
|
metadataSubset);
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void dng_image_writer::WriteTIFFWithProfile (dng_host &host,
|
|
dng_stream &stream,
|
|
const dng_image &image,
|
|
uint32 photometricInterpretation,
|
|
uint32 compression,
|
|
const dng_metadata *constMetadata,
|
|
const void *profileData,
|
|
uint32 profileSize,
|
|
const dng_resolution *resolution,
|
|
const dng_jpeg_preview *thumbnail,
|
|
const dng_memory_block *imageResources,
|
|
dng_metadata_subset metadataSubset)
|
|
{
|
|
|
|
uint32 j;
|
|
|
|
AutoPtr<dng_metadata> metadata;
|
|
|
|
if (constMetadata)
|
|
{
|
|
|
|
metadata.Reset (constMetadata->Clone (host.Allocator ()));
|
|
|
|
CleanUpMetadata (host,
|
|
*metadata,
|
|
metadataSubset,
|
|
"image/tiff");
|
|
|
|
}
|
|
|
|
dng_ifd ifd;
|
|
|
|
ifd.fNewSubFileType = sfMainImage;
|
|
|
|
ifd.fImageWidth = image.Bounds ().W ();
|
|
ifd.fImageLength = image.Bounds ().H ();
|
|
|
|
ifd.fSamplesPerPixel = image.Planes ();
|
|
|
|
ifd.fBitsPerSample [0] = TagTypeSize (image.PixelType ()) * 8;
|
|
|
|
for (j = 1; j < ifd.fSamplesPerPixel; j++)
|
|
{
|
|
ifd.fBitsPerSample [j] = ifd.fBitsPerSample [0];
|
|
}
|
|
|
|
ifd.fPhotometricInterpretation = photometricInterpretation;
|
|
|
|
ifd.fCompression = compression;
|
|
|
|
if (ifd.fCompression == ccUncompressed)
|
|
{
|
|
|
|
ifd.SetSingleStrip ();
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
|
|
ifd.FindStripSize (128 * 1024);
|
|
|
|
ifd.fPredictor = cpHorizontalDifference;
|
|
|
|
}
|
|
|
|
uint32 extraSamples = 0;
|
|
|
|
switch (photometricInterpretation)
|
|
{
|
|
|
|
case piBlackIsZero:
|
|
{
|
|
extraSamples = image.Planes () - 1;
|
|
break;
|
|
}
|
|
|
|
case piRGB:
|
|
{
|
|
extraSamples = image.Planes () - 3;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
|
|
ifd.fExtraSamplesCount = extraSamples;
|
|
|
|
if (image.PixelType () == ttFloat)
|
|
{
|
|
|
|
for (j = 0; j < ifd.fSamplesPerPixel; j++)
|
|
{
|
|
ifd.fSampleFormat [j] = sfFloatingPoint;
|
|
}
|
|
|
|
}
|
|
|
|
dng_tiff_directory mainIFD;
|
|
|
|
dng_basic_tag_set basic (mainIFD, ifd);
|
|
|
|
// Resolution.
|
|
|
|
dng_resolution res;
|
|
|
|
if (resolution)
|
|
{
|
|
res = *resolution;
|
|
}
|
|
|
|
tag_urational tagXResolution (tcXResolution, res.fXResolution);
|
|
tag_urational tagYResolution (tcYResolution, res.fYResolution);
|
|
|
|
tag_uint16 tagResolutionUnit (tcResolutionUnit, res.fResolutionUnit);
|
|
|
|
if (resolution)
|
|
{
|
|
mainIFD.Add (&tagXResolution );
|
|
mainIFD.Add (&tagYResolution );
|
|
mainIFD.Add (&tagResolutionUnit);
|
|
}
|
|
|
|
// ICC Profile.
|
|
|
|
tag_icc_profile iccProfileTag (profileData, profileSize);
|
|
|
|
if (iccProfileTag.Count ())
|
|
{
|
|
mainIFD.Add (&iccProfileTag);
|
|
}
|
|
|
|
// XMP metadata.
|
|
|
|
#if qDNGUseXMP
|
|
|
|
tag_xmp tagXMP (metadata.Get () ? metadata->GetXMP () : NULL);
|
|
|
|
if (tagXMP.Count ())
|
|
{
|
|
mainIFD.Add (&tagXMP);
|
|
}
|
|
|
|
#endif
|
|
|
|
// IPTC metadata.
|
|
|
|
tag_iptc tagIPTC (metadata.Get () ? metadata->IPTCData () : NULL,
|
|
metadata.Get () ? metadata->IPTCLength () : 0);
|
|
|
|
if (tagIPTC.Count ())
|
|
{
|
|
mainIFD.Add (&tagIPTC);
|
|
}
|
|
|
|
// Adobe data (thumbnail and IPTC digest)
|
|
|
|
AutoPtr<dng_memory_block> adobeData (BuildAdobeData (host,
|
|
metadata.Get (),
|
|
thumbnail,
|
|
imageResources));
|
|
|
|
tag_uint8_ptr tagAdobe (tcAdobeData,
|
|
adobeData->Buffer_uint8 (),
|
|
adobeData->LogicalSize ());
|
|
|
|
if (tagAdobe.Count ())
|
|
{
|
|
mainIFD.Add (&tagAdobe);
|
|
}
|
|
|
|
// Exif metadata.
|
|
|
|
exif_tag_set exifSet (mainIFD,
|
|
metadata.Get () && metadata->GetExif () ? *metadata->GetExif ()
|
|
: dng_exif (),
|
|
metadata.Get () ? metadata->IsMakerNoteSafe () : false,
|
|
metadata.Get () ? metadata->MakerNoteData () : NULL,
|
|
metadata.Get () ? metadata->MakerNoteLength () : 0,
|
|
false);
|
|
|
|
// Find offset to main image data.
|
|
|
|
uint32 offsetMainIFD = 8;
|
|
|
|
uint32 offsetExifData = offsetMainIFD + mainIFD.Size ();
|
|
|
|
exifSet.Locate (offsetExifData);
|
|
|
|
uint32 offsetMainData = offsetExifData + exifSet.Size ();
|
|
|
|
stream.SetWritePosition (offsetMainData);
|
|
|
|
// Write the main image data.
|
|
|
|
WriteImage (host,
|
|
ifd,
|
|
basic,
|
|
stream,
|
|
image);
|
|
|
|
// Trim the file to this length.
|
|
|
|
stream.SetLength (stream.Position ());
|
|
|
|
// TIFF has a 4G size limit.
|
|
|
|
if (stream.Length () > 0x0FFFFFFFFL)
|
|
{
|
|
ThrowImageTooBigTIFF ();
|
|
}
|
|
|
|
// Write TIFF Header.
|
|
|
|
stream.SetWritePosition (0);
|
|
|
|
stream.Put_uint16 (stream.BigEndian () ? byteOrderMM : byteOrderII);
|
|
|
|
stream.Put_uint16 (42);
|
|
|
|
stream.Put_uint32 (offsetMainIFD);
|
|
|
|
// Write the IFDs.
|
|
|
|
mainIFD.Put (stream);
|
|
|
|
exifSet.Put (stream);
|
|
|
|
stream.Flush ();
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void dng_image_writer::WriteDNG (dng_host &host,
|
|
dng_stream &stream,
|
|
dng_negative &negative,
|
|
const dng_preview_list *previewList,
|
|
uint32 maxBackwardVersion,
|
|
bool uncompressed)
|
|
{
|
|
|
|
WriteDNG (host,
|
|
stream,
|
|
negative,
|
|
negative.Metadata (),
|
|
previewList,
|
|
maxBackwardVersion,
|
|
uncompressed);
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
void dng_image_writer::WriteDNG (dng_host &host,
|
|
dng_stream &stream,
|
|
const dng_negative &negative,
|
|
const dng_metadata &constMetadata,
|
|
const dng_preview_list *previewList,
|
|
uint32 maxBackwardVersion,
|
|
bool uncompressed)
|
|
{
|
|
|
|
uint32 j;
|
|
|
|
// Clean up metadata per MWG recommendations.
|
|
|
|
AutoPtr<dng_metadata> metadata (constMetadata.Clone (host.Allocator ()));
|
|
|
|
CleanUpMetadata (host,
|
|
*metadata,
|
|
kMetadataSubset_All,
|
|
"image/dng");
|
|
|
|
// Figure out the compression to use. Most of the time this is lossless
|
|
// JPEG.
|
|
|
|
uint32 compression = uncompressed ? ccUncompressed : ccJPEG;
|
|
|
|
// Was the the original file lossy JPEG compressed?
|
|
|
|
const dng_jpeg_image *rawJPEGImage = negative.RawJPEGImage ();
|
|
|
|
// If so, can we save it using the requested compression and DNG version?
|
|
|
|
if (uncompressed || maxBackwardVersion < dngVersion_1_4_0_0)
|
|
{
|
|
|
|
if (rawJPEGImage || negative.RawJPEGImageDigest ().IsValid ())
|
|
{
|
|
|
|
rawJPEGImage = NULL;
|
|
|
|
negative.ClearRawJPEGImageDigest ();
|
|
|
|
negative.ClearRawImageDigest ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (rawJPEGImage)
|
|
{
|
|
|
|
compression = ccLossyJPEG;
|
|
|
|
}
|
|
|
|
// Are we saving the original size tags?
|
|
|
|
bool saveOriginalDefaultFinalSize = false;
|
|
bool saveOriginalBestQualityFinalSize = false;
|
|
bool saveOriginalDefaultCropSize = false;
|
|
|
|
{
|
|
|
|
// See if we are saving a proxy image.
|
|
|
|
dng_point defaultFinalSize (negative.DefaultFinalHeight (),
|
|
negative.DefaultFinalWidth ());
|
|
|
|
saveOriginalDefaultFinalSize = (negative.OriginalDefaultFinalSize () !=
|
|
defaultFinalSize);
|
|
|
|
if (saveOriginalDefaultFinalSize)
|
|
{
|
|
|
|
// If the save OriginalDefaultFinalSize tag, this changes the defaults
|
|
// for the OriginalBestQualityFinalSize and OriginalDefaultCropSize tags.
|
|
|
|
saveOriginalBestQualityFinalSize = (negative.OriginalBestQualityFinalSize () !=
|
|
defaultFinalSize);
|
|
|
|
saveOriginalDefaultCropSize = (negative.OriginalDefaultCropSizeV () !=
|
|
dng_urational (defaultFinalSize.v, 1)) ||
|
|
(negative.OriginalDefaultCropSizeH () !=
|
|
dng_urational (defaultFinalSize.h, 1));
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
|
|
// Else these two tags default to the normal non-proxy size image values.
|
|
|
|
dng_point bestQualityFinalSize (negative.BestQualityFinalHeight (),
|
|
negative.BestQualityFinalWidth ());
|
|
|
|
saveOriginalBestQualityFinalSize = (negative.OriginalBestQualityFinalSize () !=
|
|
bestQualityFinalSize);
|
|
|
|
saveOriginalDefaultCropSize = (negative.OriginalDefaultCropSizeV () !=
|
|
negative.DefaultCropSizeV ()) ||
|
|
(negative.OriginalDefaultCropSizeH () !=
|
|
negative.DefaultCropSizeH ());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Is this a floating point image that we are saving?
|
|
|
|
bool isFloatingPoint = (negative.RawImage ().PixelType () == ttFloat);
|
|
|
|
// Does this image have a transparency mask?
|
|
|
|
bool hasTransparencyMask = (negative.RawTransparencyMask () != NULL);
|
|
|
|
// Should we save a compressed 32-bit integer file?
|
|
|
|
bool isCompressed32BitInteger = (negative.RawImage ().PixelType () == ttLong) &&
|
|
(maxBackwardVersion >= dngVersion_1_4_0_0) &&
|
|
(!uncompressed);
|
|
|
|
// Figure out what main version to use.
|
|
|
|
uint32 dngVersion = dngVersion_Current;
|
|
|
|
// Don't write version 1.4 files unless we actually use some feature of the 1.4 spec.
|
|
|
|
if (dngVersion == dngVersion_1_4_0_0)
|
|
{
|
|
|
|
if (!rawJPEGImage &&
|
|
!isFloatingPoint &&
|
|
!hasTransparencyMask &&
|
|
!isCompressed32BitInteger &&
|
|
!saveOriginalDefaultFinalSize &&
|
|
!saveOriginalBestQualityFinalSize &&
|
|
!saveOriginalDefaultCropSize )
|
|
{
|
|
|
|
dngVersion = dngVersion_1_3_0_0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Figure out what backward version to use.
|
|
|
|
uint32 dngBackwardVersion = dngVersion_1_1_0_0;
|
|
|
|
#if defined(qTestRowInterleave) || defined(qTestSubTileBlockRows) || defined(qTestSubTileBlockCols)
|
|
dngBackwardVersion = Max_uint32 (dngBackwardVersion, dngVersion_1_2_0_0);
|
|
#endif
|
|
|
|
dngBackwardVersion = Max_uint32 (dngBackwardVersion,
|
|
negative.OpcodeList1 ().MinVersion (false));
|
|
|
|
dngBackwardVersion = Max_uint32 (dngBackwardVersion,
|
|
negative.OpcodeList2 ().MinVersion (false));
|
|
|
|
dngBackwardVersion = Max_uint32 (dngBackwardVersion,
|
|
negative.OpcodeList3 ().MinVersion (false));
|
|
|
|
if (negative.GetMosaicInfo () &&
|
|
negative.GetMosaicInfo ()->fCFALayout >= 6)
|
|
{
|
|
dngBackwardVersion = Max_uint32 (dngBackwardVersion, dngVersion_1_3_0_0);
|
|
}
|
|
|
|
if (rawJPEGImage || isFloatingPoint || hasTransparencyMask || isCompressed32BitInteger)
|
|
{
|
|
dngBackwardVersion = Max_uint32 (dngBackwardVersion, dngVersion_1_4_0_0);
|
|
}
|
|
|
|
if (dngBackwardVersion > dngVersion)
|
|
{
|
|
ThrowProgramError ();
|
|
}
|
|
|
|
// Find best thumbnail from preview list, if any.
|
|
|
|
const dng_preview *thumbnail = NULL;
|
|
|
|
if (previewList)
|
|
{
|
|
|
|
uint32 thumbArea = 0;
|
|
|
|
for (j = 0; j < previewList->Count (); j++)
|
|
{
|
|
|
|
const dng_image_preview *imagePreview = dynamic_cast<const dng_image_preview *>(&previewList->Preview (j));
|
|
|
|
if (imagePreview)
|
|
{
|
|
|
|
uint32 thisArea = imagePreview->fImage->Bounds ().W () *
|
|
imagePreview->fImage->Bounds ().H ();
|
|
|
|
if (!thumbnail || thisArea < thumbArea)
|
|
{
|
|
|
|
thumbnail = &previewList->Preview (j);
|
|
|
|
thumbArea = thisArea;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const dng_jpeg_preview *jpegPreview = dynamic_cast<const dng_jpeg_preview *>(&previewList->Preview (j));
|
|
|
|
if (jpegPreview)
|
|
{
|
|
|
|
uint32 thisArea = jpegPreview->fPreviewSize.h *
|
|
jpegPreview->fPreviewSize.v;
|
|
|
|
if (!thumbnail || thisArea < thumbArea)
|
|
{
|
|
|
|
thumbnail = &previewList->Preview (j);
|
|
|
|
thumbArea = thisArea;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Create the main IFD
|
|
|
|
dng_tiff_directory mainIFD;
|
|
|
|
// Create the IFD for the raw data. If there is no thumnail, this is
|
|
// just a reference the main IFD. Otherwise allocate a new one.
|
|
|
|
AutoPtr<dng_tiff_directory> rawIFD_IfNotMain;
|
|
|
|
if (thumbnail)
|
|
{
|
|
rawIFD_IfNotMain.Reset (new dng_tiff_directory);
|
|
}
|
|
|
|
dng_tiff_directory &rawIFD (thumbnail ? *rawIFD_IfNotMain : mainIFD);
|
|
|
|
// Include DNG version tags.
|
|
|
|
uint8 dngVersionData [4];
|
|
|
|
dngVersionData [0] = (uint8) (dngVersion >> 24);
|
|
dngVersionData [1] = (uint8) (dngVersion >> 16);
|
|
dngVersionData [2] = (uint8) (dngVersion >> 8);
|
|
dngVersionData [3] = (uint8) (dngVersion );
|
|
|
|
tag_uint8_ptr tagDNGVersion (tcDNGVersion, dngVersionData, 4);
|
|
|
|
mainIFD.Add (&tagDNGVersion);
|
|
|
|
uint8 dngBackwardVersionData [4];
|
|
|
|
dngBackwardVersionData [0] = (uint8) (dngBackwardVersion >> 24);
|
|
dngBackwardVersionData [1] = (uint8) (dngBackwardVersion >> 16);
|
|
dngBackwardVersionData [2] = (uint8) (dngBackwardVersion >> 8);
|
|
dngBackwardVersionData [3] = (uint8) (dngBackwardVersion );
|
|
|
|
tag_uint8_ptr tagDNGBackwardVersion (tcDNGBackwardVersion, dngBackwardVersionData, 4);
|
|
|
|
mainIFD.Add (&tagDNGBackwardVersion);
|
|
|
|
// The main IFD contains the thumbnail, if there is a thumbnail.
|
|
|
|
AutoPtr<dng_basic_tag_set> thmBasic;
|
|
|
|
if (thumbnail)
|
|
{
|
|
thmBasic.Reset (thumbnail->AddTagSet (mainIFD));
|
|
}
|
|
|
|
// Get the raw image we are writing.
|
|
|
|
const dng_image &rawImage (negative.RawImage ());
|
|
|
|
// For floating point, we only support ZIP compression.
|
|
|
|
if (isFloatingPoint && !uncompressed)
|
|
{
|
|
|
|
compression = ccDeflate;
|
|
|
|
}
|
|
|
|
// For 32-bit integer images, we only support ZIP and uncompressed.
|
|
|
|
if (rawImage.PixelType () == ttLong)
|
|
{
|
|
|
|
if (isCompressed32BitInteger)
|
|
{
|
|
compression = ccDeflate;
|
|
}
|
|
|
|
else
|
|
{
|
|
compression = ccUncompressed;
|
|
}
|
|
|
|
}
|
|
|
|
// Get a copy of the mosaic info.
|
|
|
|
dng_mosaic_info mosaicInfo;
|
|
|
|
if (negative.GetMosaicInfo ())
|
|
{
|
|
mosaicInfo = *(negative.GetMosaicInfo ());
|
|
}
|
|
|
|
// Create a dng_ifd record for the raw image.
|
|
|
|
dng_ifd info;
|
|
|
|
info.fImageWidth = rawImage.Width ();
|
|
info.fImageLength = rawImage.Height ();
|
|
|
|
info.fSamplesPerPixel = rawImage.Planes ();
|
|
|
|
info.fPhotometricInterpretation = mosaicInfo.IsColorFilterArray () ? piCFA
|
|
: piLinearRaw;
|
|
|
|
info.fCompression = compression;
|
|
|
|
if (isFloatingPoint && compression == ccDeflate)
|
|
{
|
|
|
|
info.fPredictor = cpFloatingPoint;
|
|
|
|
if (mosaicInfo.IsColorFilterArray ())
|
|
{
|
|
|
|
if (mosaicInfo.fCFAPatternSize.h == 2)
|
|
{
|
|
info.fPredictor = cpFloatingPointX2;
|
|
}
|
|
|
|
else if (mosaicInfo.fCFAPatternSize.h == 4)
|
|
{
|
|
info.fPredictor = cpFloatingPointX4;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (isCompressed32BitInteger)
|
|
{
|
|
|
|
info.fPredictor = cpHorizontalDifference;
|
|
|
|
if (mosaicInfo.IsColorFilterArray ())
|
|
{
|
|
|
|
if (mosaicInfo.fCFAPatternSize.h == 2)
|
|
{
|
|
info.fPredictor = cpHorizontalDifferenceX2;
|
|
}
|
|
|
|
else if (mosaicInfo.fCFAPatternSize.h == 4)
|
|
{
|
|
info.fPredictor = cpHorizontalDifferenceX4;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
uint32 rawPixelType = rawImage.PixelType ();
|
|
|
|
if (rawPixelType == ttShort)
|
|
{
|
|
|
|
// See if we are using a linearization table with <= 256 entries, in which
|
|
// case the useful data will all fit within 8-bits.
|
|
|
|
const dng_linearization_info *rangeInfo = negative.GetLinearizationInfo ();
|
|
|
|
if (rangeInfo)
|
|
{
|
|
|
|
if (rangeInfo->fLinearizationTable.Get ())
|
|
{
|
|
|
|
uint32 entries = rangeInfo->fLinearizationTable->LogicalSize () >> 1;
|
|
|
|
if (entries <= 256)
|
|
{
|
|
|
|
rawPixelType = ttByte;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
switch (rawPixelType)
|
|
{
|
|
|
|
case ttByte:
|
|
{
|
|
info.fBitsPerSample [0] = 8;
|
|
break;
|
|
}
|
|
|
|
case ttShort:
|
|
{
|
|
info.fBitsPerSample [0] = 16;
|
|
break;
|
|
}
|
|
|
|
case ttLong:
|
|
{
|
|
info.fBitsPerSample [0] = 32;
|
|
break;
|
|
}
|
|
|
|
case ttFloat:
|
|
{
|
|
|
|
if (negative.RawFloatBitDepth () == 16)
|
|
{
|
|
info.fBitsPerSample [0] = 16;
|
|
}
|
|
|
|
else if (negative.RawFloatBitDepth () == 24)
|
|
{
|
|
info.fBitsPerSample [0] = 24;
|
|
}
|
|
|
|
else
|
|
{
|
|
info.fBitsPerSample [0] = 32;
|
|
}
|
|
|
|
for (j = 0; j < info.fSamplesPerPixel; j++)
|
|
{
|
|
info.fSampleFormat [j] = sfFloatingPoint;
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
{
|
|
ThrowProgramError ();
|
|
}
|
|
|
|
}
|
|
|
|
// For lossless JPEG compression, we often lie about the
|
|
// actual channel count to get the predictors to work across
|
|
// same color mosaic pixels.
|
|
|
|
uint32 fakeChannels = 1;
|
|
|
|
if (info.fCompression == ccJPEG)
|
|
{
|
|
|
|
if (mosaicInfo.IsColorFilterArray ())
|
|
{
|
|
|
|
if (mosaicInfo.fCFAPatternSize.h == 4)
|
|
{
|
|
fakeChannels = 4;
|
|
}
|
|
|
|
else if (mosaicInfo.fCFAPatternSize.h == 2)
|
|
{
|
|
fakeChannels = 2;
|
|
}
|
|
|
|
// However, lossless JEPG is limited to four channels,
|
|
// so compromise might be required.
|
|
|
|
while (fakeChannels * info.fSamplesPerPixel > 4 &&
|
|
fakeChannels > 1)
|
|
{
|
|
|
|
fakeChannels >>= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Figure out tile sizes.
|
|
|
|
if (rawJPEGImage)
|
|
{
|
|
|
|
DNG_ASSERT (rawPixelType == ttByte,
|
|
"Unexpected jpeg pixel type");
|
|
|
|
DNG_ASSERT (info.fImageWidth == (uint32) rawJPEGImage->fImageSize.h &&
|
|
info.fImageLength == (uint32) rawJPEGImage->fImageSize.v,
|
|
"Unexpected jpeg image size");
|
|
|
|
info.fTileWidth = rawJPEGImage->fTileSize.h;
|
|
info.fTileLength = rawJPEGImage->fTileSize.v;
|
|
|
|
info.fUsesStrips = rawJPEGImage->fUsesStrips;
|
|
|
|
info.fUsesTiles = !info.fUsesStrips;
|
|
|
|
}
|
|
|
|
else if (info.fCompression == ccJPEG)
|
|
{
|
|
|
|
info.FindTileSize (128 * 1024);
|
|
|
|
}
|
|
|
|
else if (info.fCompression == ccDeflate)
|
|
{
|
|
|
|
info.FindTileSize (512 * 1024);
|
|
|
|
}
|
|
|
|
else if (info.fCompression == ccLossyJPEG)
|
|
{
|
|
|
|
ThrowProgramError ("No JPEG compressed image");
|
|
|
|
}
|
|
|
|
// Don't use tiles for uncompressed images.
|
|
|
|
else
|
|
{
|
|
|
|
info.SetSingleStrip ();
|
|
|
|
}
|
|
|
|
#ifdef qTestRowInterleave
|
|
|
|
info.fRowInterleaveFactor = qTestRowInterleave;
|
|
|
|
#endif
|
|
|
|
#if defined(qTestSubTileBlockRows) && defined(qTestSubTileBlockCols)
|
|
|
|
info.fSubTileBlockRows = qTestSubTileBlockRows;
|
|
info.fSubTileBlockCols = qTestSubTileBlockCols;
|
|
|
|
if (fakeChannels == 2)
|
|
fakeChannels = 4;
|
|
|
|
#endif
|
|
|
|
// Basic information.
|
|
|
|
dng_basic_tag_set rawBasic (rawIFD, info);
|
|
|
|
// JPEG tables, if any.
|
|
|
|
tag_data_ptr tagJPEGTables (tcJPEGTables,
|
|
ttUndefined,
|
|
0,
|
|
NULL);
|
|
|
|
if (rawJPEGImage && rawJPEGImage->fJPEGTables.Get ())
|
|
{
|
|
|
|
tagJPEGTables.SetData (rawJPEGImage->fJPEGTables->Buffer ());
|
|
|
|
tagJPEGTables.SetCount (rawJPEGImage->fJPEGTables->LogicalSize ());
|
|
|
|
rawIFD.Add (&tagJPEGTables);
|
|
|
|
}
|
|
|
|
// DefaultScale tag.
|
|
|
|
dng_urational defaultScaleData [2];
|
|
|
|
defaultScaleData [0] = negative.DefaultScaleH ();
|
|
defaultScaleData [1] = negative.DefaultScaleV ();
|
|
|
|
tag_urational_ptr tagDefaultScale (tcDefaultScale,
|
|
defaultScaleData,
|
|
2);
|
|
|
|
rawIFD.Add (&tagDefaultScale);
|
|
|
|
// Best quality scale tag.
|
|
|
|
tag_urational tagBestQualityScale (tcBestQualityScale,
|
|
negative.BestQualityScale ());
|
|
|
|
rawIFD.Add (&tagBestQualityScale);
|
|
|
|
// DefaultCropOrigin tag.
|
|
|
|
dng_urational defaultCropOriginData [2];
|
|
|
|
defaultCropOriginData [0] = negative.DefaultCropOriginH ();
|
|
defaultCropOriginData [1] = negative.DefaultCropOriginV ();
|
|
|
|
tag_urational_ptr tagDefaultCropOrigin (tcDefaultCropOrigin,
|
|
defaultCropOriginData,
|
|
2);
|
|
|
|
rawIFD.Add (&tagDefaultCropOrigin);
|
|
|
|
// DefaultCropSize tag.
|
|
|
|
dng_urational defaultCropSizeData [2];
|
|
|
|
defaultCropSizeData [0] = negative.DefaultCropSizeH ();
|
|
defaultCropSizeData [1] = negative.DefaultCropSizeV ();
|
|
|
|
tag_urational_ptr tagDefaultCropSize (tcDefaultCropSize,
|
|
defaultCropSizeData,
|
|
2);
|
|
|
|
rawIFD.Add (&tagDefaultCropSize);
|
|
|
|
// DefaultUserCrop tag.
|
|
|
|
dng_urational defaultUserCropData [4];
|
|
|
|
defaultUserCropData [0] = negative.DefaultUserCropT ();
|
|
defaultUserCropData [1] = negative.DefaultUserCropL ();
|
|
defaultUserCropData [2] = negative.DefaultUserCropB ();
|
|
defaultUserCropData [3] = negative.DefaultUserCropR ();
|
|
|
|
tag_urational_ptr tagDefaultUserCrop (tcDefaultUserCrop,
|
|
defaultUserCropData,
|
|
4);
|
|
|
|
rawIFD.Add (&tagDefaultUserCrop);
|
|
|
|
// Range mapping tag set.
|
|
|
|
range_tag_set rangeSet (rawIFD, negative);
|
|
|
|
// Mosaic pattern information.
|
|
|
|
mosaic_tag_set mosaicSet (rawIFD, mosaicInfo);
|
|
|
|
// Chroma blur radius.
|
|
|
|
tag_urational tagChromaBlurRadius (tcChromaBlurRadius,
|
|
negative.ChromaBlurRadius ());
|
|
|
|
if (negative.ChromaBlurRadius ().IsValid ())
|
|
{
|
|
|
|
rawIFD.Add (&tagChromaBlurRadius);
|
|
|
|
}
|
|
|
|
// Anti-alias filter strength.
|
|
|
|
tag_urational tagAntiAliasStrength (tcAntiAliasStrength,
|
|
negative.AntiAliasStrength ());
|
|
|
|
if (negative.AntiAliasStrength ().IsValid ())
|
|
{
|
|
|
|
rawIFD.Add (&tagAntiAliasStrength);
|
|
|
|
}
|
|
|
|
// Profile and other color related tags.
|
|
|
|
AutoPtr<profile_tag_set> profileSet;
|
|
|
|
AutoPtr<color_tag_set> colorSet;
|
|
|
|
dng_std_vector<uint32> extraProfileIndex;
|
|
|
|
if (!negative.IsMonochrome ())
|
|
{
|
|
|
|
const dng_camera_profile &mainProfile (*negative.ComputeCameraProfileToEmbed (constMetadata));
|
|
|
|
profileSet.Reset (new profile_tag_set (mainIFD,
|
|
mainProfile));
|
|
|
|
colorSet.Reset (new color_tag_set (mainIFD,
|
|
negative));
|
|
|
|
// Build list of profile indices to include in extra profiles tag.
|
|
|
|
uint32 profileCount = negative.ProfileCount ();
|
|
|
|
for (uint32 index = 0; index < profileCount; index++)
|
|
{
|
|
|
|
const dng_camera_profile &profile (negative.ProfileByIndex (index));
|
|
|
|
if (&profile != &mainProfile)
|
|
{
|
|
|
|
if (profile.WasReadFromDNG ())
|
|
{
|
|
|
|
extraProfileIndex.push_back (index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Extra camera profiles tag.
|
|
|
|
uint32 extraProfileCount = (uint32) extraProfileIndex.size ();
|
|
|
|
dng_memory_data extraProfileOffsets (extraProfileCount, sizeof (uint32));
|
|
|
|
tag_uint32_ptr extraProfileTag (tcExtraCameraProfiles,
|
|
extraProfileOffsets.Buffer_uint32 (),
|
|
extraProfileCount);
|
|
|
|
if (extraProfileCount)
|
|
{
|
|
|
|
mainIFD.Add (&extraProfileTag);
|
|
|
|
}
|
|
|
|
// Other tags.
|
|
|
|
tag_uint16 tagOrientation (tcOrientation,
|
|
(uint16) negative.ComputeOrientation (constMetadata).GetTIFF ());
|
|
|
|
mainIFD.Add (&tagOrientation);
|
|
|
|
tag_srational tagBaselineExposure (tcBaselineExposure,
|
|
negative.BaselineExposureR ());
|
|
|
|
mainIFD.Add (&tagBaselineExposure);
|
|
|
|
tag_urational tagBaselineNoise (tcBaselineNoise,
|
|
negative.BaselineNoiseR ());
|
|
|
|
mainIFD.Add (&tagBaselineNoise);
|
|
|
|
tag_urational tagNoiseReductionApplied (tcNoiseReductionApplied,
|
|
negative.NoiseReductionApplied ());
|
|
|
|
if (negative.NoiseReductionApplied ().IsValid ())
|
|
{
|
|
|
|
mainIFD.Add (&tagNoiseReductionApplied);
|
|
|
|
}
|
|
|
|
tag_dng_noise_profile tagNoiseProfile (negative.NoiseProfile ());
|
|
|
|
if (negative.NoiseProfile ().IsValidForNegative (negative))
|
|
{
|
|
|
|
mainIFD.Add (&tagNoiseProfile);
|
|
|
|
}
|
|
|
|
tag_urational tagBaselineSharpness (tcBaselineSharpness,
|
|
negative.BaselineSharpnessR ());
|
|
|
|
mainIFD.Add (&tagBaselineSharpness);
|
|
|
|
tag_string tagUniqueName (tcUniqueCameraModel,
|
|
negative.ModelName (),
|
|
true);
|
|
|
|
mainIFD.Add (&tagUniqueName);
|
|
|
|
tag_string tagLocalName (tcLocalizedCameraModel,
|
|
negative.LocalName (),
|
|
false);
|
|
|
|
if (negative.LocalName ().NotEmpty ())
|
|
{
|
|
|
|
mainIFD.Add (&tagLocalName);
|
|
|
|
}
|
|
|
|
tag_urational tagShadowScale (tcShadowScale,
|
|
negative.ShadowScaleR ());
|
|
|
|
mainIFD.Add (&tagShadowScale);
|
|
|
|
tag_uint16 tagColorimetricReference (tcColorimetricReference,
|
|
(uint16) negative.ColorimetricReference ());
|
|
|
|
if (negative.ColorimetricReference () != crSceneReferred)
|
|
{
|
|
|
|
mainIFD.Add (&tagColorimetricReference);
|
|
|
|
}
|
|
|
|
bool useNewDigest = (maxBackwardVersion >= dngVersion_1_4_0_0);
|
|
|
|
if (compression == ccLossyJPEG)
|
|
{
|
|
|
|
negative.FindRawJPEGImageDigest (host);
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
|
|
if (useNewDigest)
|
|
{
|
|
negative.FindNewRawImageDigest (host);
|
|
}
|
|
else
|
|
{
|
|
negative.FindRawImageDigest (host);
|
|
}
|
|
|
|
}
|
|
|
|
tag_uint8_ptr tagRawImageDigest (useNewDigest ? tcNewRawImageDigest : tcRawImageDigest,
|
|
compression == ccLossyJPEG ?
|
|
negative.RawJPEGImageDigest ().data :
|
|
(useNewDigest ? negative.NewRawImageDigest ().data
|
|
: negative.RawImageDigest ().data),
|
|
16);
|
|
|
|
mainIFD.Add (&tagRawImageDigest);
|
|
|
|
negative.FindRawDataUniqueID (host);
|
|
|
|
tag_uint8_ptr tagRawDataUniqueID (tcRawDataUniqueID,
|
|
negative.RawDataUniqueID ().data,
|
|
16);
|
|
|
|
if (negative.RawDataUniqueID ().IsValid ())
|
|
{
|
|
|
|
mainIFD.Add (&tagRawDataUniqueID);
|
|
|
|
}
|
|
|
|
tag_string tagOriginalRawFileName (tcOriginalRawFileName,
|
|
negative.OriginalRawFileName (),
|
|
false);
|
|
|
|
if (negative.HasOriginalRawFileName ())
|
|
{
|
|
|
|
mainIFD.Add (&tagOriginalRawFileName);
|
|
|
|
}
|
|
|
|
negative.FindOriginalRawFileDigest ();
|
|
|
|
tag_data_ptr tagOriginalRawFileData (tcOriginalRawFileData,
|
|
ttUndefined,
|
|
negative.OriginalRawFileDataLength (),
|
|
negative.OriginalRawFileData ());
|
|
|
|
tag_uint8_ptr tagOriginalRawFileDigest (tcOriginalRawFileDigest,
|
|
negative.OriginalRawFileDigest ().data,
|
|
16);
|
|
|
|
if (negative.OriginalRawFileData ())
|
|
{
|
|
|
|
mainIFD.Add (&tagOriginalRawFileData);
|
|
|
|
mainIFD.Add (&tagOriginalRawFileDigest);
|
|
|
|
}
|
|
|
|
// XMP metadata.
|
|
|
|
#if qDNGUseXMP
|
|
|
|
tag_xmp tagXMP (metadata->GetXMP ());
|
|
|
|
if (tagXMP.Count ())
|
|
{
|
|
|
|
mainIFD.Add (&tagXMP);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
// Exif tags.
|
|
|
|
exif_tag_set exifSet (mainIFD,
|
|
*metadata->GetExif (),
|
|
metadata->IsMakerNoteSafe (),
|
|
metadata->MakerNoteData (),
|
|
metadata->MakerNoteLength (),
|
|
true);
|
|
|
|
// Private data.
|
|
|
|
tag_uint8_ptr tagPrivateData (tcDNGPrivateData,
|
|
negative.PrivateData (),
|
|
negative.PrivateLength ());
|
|
|
|
if (negative.PrivateLength ())
|
|
{
|
|
|
|
mainIFD.Add (&tagPrivateData);
|
|
|
|
}
|
|
|
|
// Proxy size tags.
|
|
|
|
uint32 originalDefaultFinalSizeData [2];
|
|
|
|
originalDefaultFinalSizeData [0] = negative.OriginalDefaultFinalSize ().h;
|
|
originalDefaultFinalSizeData [1] = negative.OriginalDefaultFinalSize ().v;
|
|
|
|
tag_uint32_ptr tagOriginalDefaultFinalSize (tcOriginalDefaultFinalSize,
|
|
originalDefaultFinalSizeData,
|
|
2);
|
|
|
|
if (saveOriginalDefaultFinalSize)
|
|
{
|
|
|
|
mainIFD.Add (&tagOriginalDefaultFinalSize);
|
|
|
|
}
|
|
|
|
uint32 originalBestQualityFinalSizeData [2];
|
|
|
|
originalBestQualityFinalSizeData [0] = negative.OriginalBestQualityFinalSize ().h;
|
|
originalBestQualityFinalSizeData [1] = negative.OriginalBestQualityFinalSize ().v;
|
|
|
|
tag_uint32_ptr tagOriginalBestQualityFinalSize (tcOriginalBestQualityFinalSize,
|
|
originalBestQualityFinalSizeData,
|
|
2);
|
|
|
|
if (saveOriginalBestQualityFinalSize)
|
|
{
|
|
|
|
mainIFD.Add (&tagOriginalBestQualityFinalSize);
|
|
|
|
}
|
|
|
|
dng_urational originalDefaultCropSizeData [2];
|
|
|
|
originalDefaultCropSizeData [0] = negative.OriginalDefaultCropSizeH ();
|
|
originalDefaultCropSizeData [1] = negative.OriginalDefaultCropSizeV ();
|
|
|
|
tag_urational_ptr tagOriginalDefaultCropSize (tcOriginalDefaultCropSize,
|
|
originalDefaultCropSizeData,
|
|
2);
|
|
|
|
if (saveOriginalDefaultCropSize)
|
|
{
|
|
|
|
mainIFD.Add (&tagOriginalDefaultCropSize);
|
|
|
|
}
|
|
|
|
// Opcode list 1.
|
|
|
|
AutoPtr<dng_memory_block> opcodeList1Data (negative.OpcodeList1 ().Spool (host));
|
|
|
|
tag_data_ptr tagOpcodeList1 (tcOpcodeList1,
|
|
ttUndefined,
|
|
opcodeList1Data.Get () ? opcodeList1Data->LogicalSize () : 0,
|
|
opcodeList1Data.Get () ? opcodeList1Data->Buffer () : NULL);
|
|
|
|
if (opcodeList1Data.Get ())
|
|
{
|
|
|
|
rawIFD.Add (&tagOpcodeList1);
|
|
|
|
}
|
|
|
|
// Opcode list 2.
|
|
|
|
AutoPtr<dng_memory_block> opcodeList2Data (negative.OpcodeList2 ().Spool (host));
|
|
|
|
tag_data_ptr tagOpcodeList2 (tcOpcodeList2,
|
|
ttUndefined,
|
|
opcodeList2Data.Get () ? opcodeList2Data->LogicalSize () : 0,
|
|
opcodeList2Data.Get () ? opcodeList2Data->Buffer () : NULL);
|
|
|
|
if (opcodeList2Data.Get ())
|
|
{
|
|
|
|
rawIFD.Add (&tagOpcodeList2);
|
|
|
|
}
|
|
|
|
// Opcode list 3.
|
|
|
|
AutoPtr<dng_memory_block> opcodeList3Data (negative.OpcodeList3 ().Spool (host));
|
|
|
|
tag_data_ptr tagOpcodeList3 (tcOpcodeList3,
|
|
ttUndefined,
|
|
opcodeList3Data.Get () ? opcodeList3Data->LogicalSize () : 0,
|
|
opcodeList3Data.Get () ? opcodeList3Data->Buffer () : NULL);
|
|
|
|
if (opcodeList3Data.Get ())
|
|
{
|
|
|
|
rawIFD.Add (&tagOpcodeList3);
|
|
|
|
}
|
|
|
|
// Transparency mask, if any.
|
|
|
|
AutoPtr<dng_ifd> maskInfo;
|
|
|
|
AutoPtr<dng_tiff_directory> maskIFD;
|
|
|
|
AutoPtr<dng_basic_tag_set> maskBasic;
|
|
|
|
if (hasTransparencyMask)
|
|
{
|
|
|
|
// Create mask IFD.
|
|
|
|
maskInfo.Reset (new dng_ifd);
|
|
|
|
maskInfo->fNewSubFileType = sfTransparencyMask;
|
|
|
|
maskInfo->fImageWidth = negative.RawTransparencyMask ()->Bounds ().W ();
|
|
maskInfo->fImageLength = negative.RawTransparencyMask ()->Bounds ().H ();
|
|
|
|
maskInfo->fSamplesPerPixel = 1;
|
|
|
|
maskInfo->fBitsPerSample [0] = negative.RawTransparencyMaskBitDepth ();
|
|
|
|
maskInfo->fPhotometricInterpretation = piTransparencyMask;
|
|
|
|
maskInfo->fCompression = uncompressed ? ccUncompressed : ccDeflate;
|
|
maskInfo->fPredictor = uncompressed ? cpNullPredictor : cpHorizontalDifference;
|
|
|
|
if (negative.RawTransparencyMask ()->PixelType () == ttFloat)
|
|
{
|
|
|
|
maskInfo->fSampleFormat [0] = sfFloatingPoint;
|
|
|
|
if (maskInfo->fCompression == ccDeflate)
|
|
{
|
|
maskInfo->fPredictor = cpFloatingPoint;
|
|
}
|
|
|
|
}
|
|
|
|
if (maskInfo->fCompression == ccDeflate)
|
|
{
|
|
maskInfo->FindTileSize (512 * 1024);
|
|
}
|
|
else
|
|
{
|
|
maskInfo->SetSingleStrip ();
|
|
}
|
|
|
|
// Create mask tiff directory.
|
|
|
|
maskIFD.Reset (new dng_tiff_directory);
|
|
|
|
// Add mask basic tag set.
|
|
|
|
maskBasic.Reset (new dng_basic_tag_set (*maskIFD, *maskInfo));
|
|
|
|
}
|
|
|
|
// Add other subfiles.
|
|
|
|
uint32 subFileCount = thumbnail ? 1 : 0;
|
|
|
|
if (hasTransparencyMask)
|
|
{
|
|
subFileCount++;
|
|
}
|
|
|
|
// Add previews.
|
|
|
|
uint32 previewCount = previewList ? previewList->Count () : 0;
|
|
|
|
AutoPtr<dng_tiff_directory> previewIFD [kMaxDNGPreviews];
|
|
|
|
AutoPtr<dng_basic_tag_set> previewBasic [kMaxDNGPreviews];
|
|
|
|
for (j = 0; j < previewCount; j++)
|
|
{
|
|
|
|
if (thumbnail != &previewList->Preview (j))
|
|
{
|
|
|
|
previewIFD [j] . Reset (new dng_tiff_directory);
|
|
|
|
previewBasic [j] . Reset (previewList->Preview (j).AddTagSet (*previewIFD [j]));
|
|
|
|
subFileCount++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// And a link to the raw and JPEG image IFDs.
|
|
|
|
uint32 subFileData [kMaxDNGPreviews + 2];
|
|
|
|
tag_uint32_ptr tagSubFile (tcSubIFDs,
|
|
subFileData,
|
|
subFileCount);
|
|
|
|
if (subFileCount)
|
|
{
|
|
|
|
mainIFD.Add (&tagSubFile);
|
|
|
|
}
|
|
|
|
// Skip past the header and IFDs for now.
|
|
|
|
uint32 currentOffset = 8;
|
|
|
|
currentOffset += mainIFD.Size ();
|
|
|
|
uint32 subFileIndex = 0;
|
|
|
|
if (thumbnail)
|
|
{
|
|
|
|
subFileData [subFileIndex++] = currentOffset;
|
|
|
|
currentOffset += rawIFD.Size ();
|
|
|
|
}
|
|
|
|
if (hasTransparencyMask)
|
|
{
|
|
|
|
subFileData [subFileIndex++] = currentOffset;
|
|
|
|
currentOffset += maskIFD->Size ();
|
|
|
|
}
|
|
|
|
for (j = 0; j < previewCount; j++)
|
|
{
|
|
|
|
if (thumbnail != &previewList->Preview (j))
|
|
{
|
|
|
|
subFileData [subFileIndex++] = currentOffset;
|
|
|
|
currentOffset += previewIFD [j]->Size ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
exifSet.Locate (currentOffset);
|
|
|
|
currentOffset += exifSet.Size ();
|
|
|
|
stream.SetWritePosition (currentOffset);
|
|
|
|
// Write the extra profiles.
|
|
|
|
if (extraProfileCount)
|
|
{
|
|
|
|
for (j = 0; j < extraProfileCount; j++)
|
|
{
|
|
|
|
extraProfileOffsets.Buffer_uint32 () [j] = (uint32) stream.Position ();
|
|
|
|
uint32 index = extraProfileIndex [j];
|
|
|
|
const dng_camera_profile &profile (negative.ProfileByIndex (index));
|
|
|
|
tiff_dng_extended_color_profile extraWriter (profile);
|
|
|
|
extraWriter.Put (stream, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Write the thumbnail data.
|
|
|
|
if (thumbnail)
|
|
{
|
|
|
|
thumbnail->WriteData (host,
|
|
*this,
|
|
*thmBasic,
|
|
stream);
|
|
|
|
}
|
|
|
|
// Write the preview data.
|
|
|
|
for (j = 0; j < previewCount; j++)
|
|
{
|
|
|
|
if (thumbnail != &previewList->Preview (j))
|
|
{
|
|
|
|
previewList->Preview (j).WriteData (host,
|
|
*this,
|
|
*previewBasic [j],
|
|
stream);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Write the raw data.
|
|
|
|
if (rawJPEGImage)
|
|
{
|
|
|
|
uint32 tileCount = info.TilesAcross () *
|
|
info.TilesDown ();
|
|
|
|
for (uint32 tileIndex = 0; tileIndex < tileCount; tileIndex++)
|
|
{
|
|
|
|
// Remember this offset.
|
|
|
|
uint32 tileOffset = (uint32) stream.Position ();
|
|
|
|
rawBasic.SetTileOffset (tileIndex, tileOffset);
|
|
|
|
// Write JPEG data.
|
|
|
|
stream.Put (rawJPEGImage->fJPEGData [tileIndex]->Buffer (),
|
|
rawJPEGImage->fJPEGData [tileIndex]->LogicalSize ());
|
|
|
|
// Update tile count.
|
|
|
|
uint32 tileByteCount = (uint32) stream.Position () - tileOffset;
|
|
|
|
rawBasic.SetTileByteCount (tileIndex, tileByteCount);
|
|
|
|
// Keep the tiles on even byte offsets.
|
|
|
|
if (tileByteCount & 1)
|
|
{
|
|
stream.Put_uint8 (0);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
{
|
|
|
|
#if qDNGValidate
|
|
dng_timer timer ("Write raw image time");
|
|
#endif
|
|
|
|
WriteImage (host,
|
|
info,
|
|
rawBasic,
|
|
stream,
|
|
rawImage,
|
|
fakeChannels);
|
|
|
|
}
|
|
|
|
// Write transparency mask image.
|
|
|
|
if (hasTransparencyMask)
|
|
{
|
|
|
|
#if qDNGValidate
|
|
dng_timer timer ("Write transparency mask time");
|
|
#endif
|
|
|
|
WriteImage (host,
|
|
*maskInfo,
|
|
*maskBasic,
|
|
stream,
|
|
*negative.RawTransparencyMask ());
|
|
|
|
}
|
|
|
|
// Trim the file to this length.
|
|
|
|
stream.SetLength (stream.Position ());
|
|
|
|
// DNG has a 4G size limit.
|
|
|
|
if (stream.Length () > 0x0FFFFFFFFL)
|
|
{
|
|
ThrowImageTooBigDNG ();
|
|
}
|
|
|
|
// Write TIFF Header.
|
|
|
|
stream.SetWritePosition (0);
|
|
|
|
stream.Put_uint16 (stream.BigEndian () ? byteOrderMM : byteOrderII);
|
|
|
|
stream.Put_uint16 (42);
|
|
|
|
stream.Put_uint32 (8);
|
|
|
|
// Write the IFDs.
|
|
|
|
mainIFD.Put (stream);
|
|
|
|
if (thumbnail)
|
|
{
|
|
|
|
rawIFD.Put (stream);
|
|
|
|
}
|
|
|
|
if (hasTransparencyMask)
|
|
{
|
|
|
|
maskIFD->Put (stream);
|
|
|
|
}
|
|
|
|
for (j = 0; j < previewCount; j++)
|
|
{
|
|
|
|
if (thumbnail != &previewList->Preview (j))
|
|
{
|
|
|
|
previewIFD [j]->Put (stream);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
exifSet.Put (stream);
|
|
|
|
stream.Flush ();
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|