blob: 9ea94df266a6d83986c6fc4a90af0afee0f8247c [file] [log] [blame] [edit]
/*****************************************************************************/
// Copyright 2007-2022 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.
/*****************************************************************************/
#include "dng_preview.h"
#include "dng_assertions.h"
#include "dng_host.h"
#include "dng_image.h"
#include "dng_image_writer.h"
#include "dng_jxl.h"
#include "dng_memory.h"
#include "dng_stream.h"
#include "dng_tag_codes.h"
#include "dng_tag_values.h"
/*****************************************************************************/
class dng_preview_tag_set: public dng_basic_tag_set
{
private:
tag_string fApplicationNameTag;
tag_string fApplicationVersionTag;
tag_string fSettingsNameTag;
dng_fingerprint fSettingsDigest;
tag_uint8_ptr fSettingsDigestTag;
tag_uint32 fColorSpaceTag;
tag_string fDateTimeTag;
tag_real64 fRawToPreviewGainTag;
tag_uint32 fCacheVersionTag;
public:
dng_preview_tag_set (dng_tiff_directory &directory,
const dng_preview &preview,
const dng_ifd &ifd);
virtual ~dng_preview_tag_set ();
};
/*****************************************************************************/
dng_preview_tag_set::dng_preview_tag_set (dng_tiff_directory &directory,
const dng_preview &preview,
const dng_ifd &ifd)
: dng_basic_tag_set (directory, ifd)
, fApplicationNameTag (tcPreviewApplicationName,
preview.fInfo.fApplicationName,
false)
, fApplicationVersionTag (tcPreviewApplicationVersion,
preview.fInfo.fApplicationVersion,
false)
, fSettingsNameTag (tcPreviewSettingsName,
preview.fInfo.fSettingsName,
false)
, fSettingsDigest (preview.fInfo.fSettingsDigest)
, fSettingsDigestTag (tcPreviewSettingsDigest,
fSettingsDigest.data,
16)
, fColorSpaceTag (tcPreviewColorSpace,
preview.fInfo.fColorSpace)
, fDateTimeTag (tcPreviewDateTime,
preview.fInfo.fDateTime,
true)
, fRawToPreviewGainTag (tcRawToPreviewGain,
preview.fInfo.fRawToPreviewGain)
, fCacheVersionTag (tcCacheVersion,
preview.fInfo.fCacheVersion)
{
if (preview.fInfo.fApplicationName.NotEmpty ())
{
directory.Add (&fApplicationNameTag);
}
if (preview.fInfo.fApplicationVersion.NotEmpty ())
{
directory.Add (&fApplicationVersionTag);
}
if (preview.fInfo.fSettingsName.NotEmpty ())
{
directory.Add (&fSettingsNameTag);
}
if (preview.fInfo.fSettingsDigest.IsValid ())
{
directory.Add (&fSettingsDigestTag);
}
if (preview.fInfo.fColorSpace != previewColorSpace_MaxEnum)
{
directory.Add (&fColorSpaceTag);
}
if (preview.fInfo.fDateTime.NotEmpty ())
{
directory.Add (&fDateTimeTag);
}
if (preview.fInfo.fRawToPreviewGain != 1.0)
{
directory.Add (&fRawToPreviewGainTag);
}
if (preview.fInfo.fCacheVersion != 0)
{
directory.Add (&fCacheVersionTag);
}
}
/*****************************************************************************/
dng_preview_tag_set::~dng_preview_tag_set ()
{
}
/*****************************************************************************/
void dng_preview::SetIFDInfo (dng_host & /* host */,
const dng_image &image)
{
fIFD.fNewSubFileType = fInfo.fIsPrimary ? sfPreviewImage
: sfAltPreviewImage;
fIFD.fImageWidth = image.Width ();
fIFD.fImageLength = image.Height ();
fIFD.fSamplesPerPixel = image.Planes ();
fIFD.fPhotometricInterpretation = fIFD.fSamplesPerPixel == 1 ? piBlackIsZero
: piRGB;
fIFD.fBitsPerSample [0] = TagTypeSize (image.PixelType ()) * 8;
fIFD.fSampleFormat [0] = image.PixelType () == ttFloat
? sfFloatingPoint
: sfUnsignedInteger;
for (uint32 j = 1; j < fIFD.fSamplesPerPixel; j++)
{
fIFD.fBitsPerSample [j] = fIFD.fBitsPerSample [0];
fIFD.fSampleFormat [j] = fIFD.fSampleFormat [0];
}
fIFD.SetSingleStrip ();
}
/*****************************************************************************/
dng_basic_tag_set * dng_preview::AddTagSet (dng_host & /* host */,
dng_tiff_directory &directory) const
{
return new dng_preview_tag_set (directory, *this, fIFD);
}
/*****************************************************************************/
void dng_preview::WriteData (dng_host &host,
dng_image_writer &writer,
dng_basic_tag_set &basic,
dng_stream &stream) const
{
if (fCompressedImage.get ())
{
fCompressedImage->WriteData (stream,
basic);
}
else
{
writer.WriteImage (host,
fIFD,
basic,
stream,
*fImage);
}
}
/*****************************************************************************/
uint64 dng_preview::MaxImageDataByteCount () const
{
if (fCompressedImage.get ())
{
return fCompressedImage->NonHeaderSize ();
}
else
{
return fIFD.MaxImageDataByteCount ();
}
}
/*****************************************************************************/
void dng_preview::Compress (dng_host &host,
dng_image_writer &writer)
{
if (fIFD.fCompression == ccJXL ||
fIFD.fCompression == ccLossyJPEG ||
fIFD.fCompression == ccDeflate)
{
AutoPtr<dng_compressed_image_tiles> compressedImage
(new dng_compressed_image_tiles);
compressedImage->EncodeTiles (host,
writer,
*fImage,
fIFD);
fCompressedImage.reset (compressedImage.Release ());
fImage.reset ();
}
}
/*****************************************************************************/
class dng_jpeg_preview_tag_set: public dng_preview_tag_set
{
private:
dng_urational fCoefficientsData [3];
tag_urational_ptr fCoefficientsTag;
uint16 fSubSamplingData [2];
tag_uint16_ptr fSubSamplingTag;
tag_uint16 fPositioningTag;
dng_urational fReferenceData [6];
tag_urational_ptr fReferenceTag;
public:
dng_jpeg_preview_tag_set (dng_tiff_directory &directory,
const dng_jpeg_preview &preview,
const dng_ifd &ifd);
};
/******************************************************************************/
dng_jpeg_preview_tag_set::dng_jpeg_preview_tag_set (dng_tiff_directory &directory,
const dng_jpeg_preview &preview,
const dng_ifd &ifd)
: dng_preview_tag_set (directory, preview, ifd)
, fCoefficientsTag (tcYCbCrCoefficients, fCoefficientsData, 3)
, fSubSamplingTag (tcYCbCrSubSampling, fSubSamplingData, 2)
, fPositioningTag (tcYCbCrPositioning, ifd.fYCbCrPositioning)
, fReferenceTag (tcReferenceBlackWhite, fReferenceData, 6)
{
if (ifd.fPhotometricInterpretation == piYCbCr)
{
fCoefficientsData [0] = dng_urational (299, 1000);
fCoefficientsData [1] = dng_urational (587, 1000);
fCoefficientsData [2] = dng_urational (114, 1000);
directory.Add (&fCoefficientsTag);
fSubSamplingData [0] = (uint16) ifd.fYCbCrSubSampleH;
fSubSamplingData [1] = (uint16) ifd.fYCbCrSubSampleV;
directory.Add (&fSubSamplingTag);
directory.Add (&fPositioningTag);
fReferenceData [0] = dng_urational ( 0, 1);
fReferenceData [1] = dng_urational (255, 1);
fReferenceData [2] = dng_urational (128, 1);
fReferenceData [3] = dng_urational (255, 1);
fReferenceData [4] = dng_urational (128, 1);
fReferenceData [5] = dng_urational (255, 1);
directory.Add (&fReferenceTag);
}
}
/*****************************************************************************/
void dng_jpeg_preview::SetIFDInfo (dng_host &host,
const dng_image &image)
{
dng_preview::SetIFDInfo (host, image);
fIFD.fCompression = ccJPEG; // Lossy version
if (image.Planes () == 1)
{
fIFD.fPhotometricInterpretation = piBlackIsZero;
}
else
{
SetYCbCr (1, 1);
}
}
/*****************************************************************************/
void dng_jpeg_preview::SetCompressedData (AutoPtr<dng_memory_block> &compressedData)
{
fImage.reset ();
AutoPtr<dng_compressed_image_tiles> compressedTiles (new dng_compressed_image_tiles);
compressedTiles->fData.resize (1);
compressedTiles->fData [0].reset (compressedData.Release ());
fCompressedImage.reset (compressedTiles.Release ());
}
/*****************************************************************************/
const dng_memory_block & dng_jpeg_preview::CompressedData () const
{
DNG_REQUIRE (fCompressedImage.get () &&
fCompressedImage->fData.size () == 1,
"No compressed data");
return *(fCompressedImage->fData [0]);
}
/*****************************************************************************/
dng_basic_tag_set * dng_jpeg_preview::AddTagSet (dng_host & /* host */,
dng_tiff_directory &directory) const
{
return new dng_jpeg_preview_tag_set (directory, *this, fIFD);
}
/*****************************************************************************/
void dng_jpeg_preview::WriteData (dng_host &host,
dng_image_writer &writer,
dng_basic_tag_set &basic,
dng_stream &stream) const
{
// Force the data to be written using lossy JPEG.
fIFD.fCompression = ccLossyJPEG;
dng_preview::WriteData (host,
writer,
basic,
stream);
// But we still want to use the normal jpeg compression code in the IFD.
fIFD.fCompression = ccJPEG;
}
/*****************************************************************************/
uint64 dng_jpeg_preview::MaxImageDataByteCount () const
{
fIFD.fCompression = ccLossyJPEG;
uint64 result = dng_preview::MaxImageDataByteCount ();
fIFD.fCompression = ccJPEG;
return result;
}
/*****************************************************************************/
void dng_jpeg_preview::Compress (dng_host &host,
dng_image_writer &writer)
{
fIFD.fCompression = ccLossyJPEG;
dng_preview::Compress (host, writer);
fIFD.fCompression = ccJPEG;
}
/*****************************************************************************/
void dng_jpeg_preview::SpoolAdobeThumbnail (dng_stream &stream) const
{
DNG_ASSERT (fIFD.fPhotometricInterpretation == piYCbCr,
"SpoolAdobeThumbnail: Non-YCbCr");
uint32 compressedSize = CompressedData ().LogicalSize ();
stream.Put_uint32 (DNG_CHAR4 ('8','B','I','M'));
stream.Put_uint16 (1036);
stream.Put_uint16 (0);
stream.Put_uint32 (compressedSize + 28);
uint32 widthBytes = (fIFD.fImageWidth * 24 + 31) / 32 * 4;
stream.Put_uint32 (1);
stream.Put_uint32 (fIFD.fImageWidth);
stream.Put_uint32 (fIFD.fImageLength);
stream.Put_uint32 (widthBytes);
stream.Put_uint32 (widthBytes * fIFD.fImageLength);
stream.Put_uint32 (compressedSize);
stream.Put_uint16 (24);
stream.Put_uint16 (1);
stream.Put (CompressedData ().Buffer (),
compressedSize);
if (compressedSize & 1)
{
stream.Put_uint8 (0);
}
}
//*****************************************************************************/
void dng_jxl_preview::SetIFDInfo (dng_host &host,
const dng_image &image)
{
dng_preview::SetIFDInfo (host, image);
// Store a copy of the preview info so that the writer can get information
// about the color space.
fIFD.fPreviewInfo = this->fInfo;
fIFD.fCompression = ccJXL;
fIFD.fPredictor = cpNullPredictor;
AutoPtr<dng_jxl_encode_settings> settings (host.MakeJXLEncodeSettings (dng_host::use_case_RenderedPreview,
image));
fIFD.fJXLEncodeSettings.reset (settings.Release ());
fIFD.fJXLDistance = fIFD.fJXLEncodeSettings->Distance ();
fIFD.fJXLEffort = fIFD.fJXLEncodeSettings->Effort ();
fIFD.fJXLDecodeSpeed = fIFD.fJXLEncodeSettings->DecodeSpeed ();
}
/*****************************************************************************/
class dng_raw_preview_tag_set: public dng_preview_tag_set
{
private:
tag_data_ptr fOpcodeList2Tag;
tag_uint32_ptr fWhiteLevelTag;
uint32 fWhiteLevelData [kMaxColorPlanes];
tag_urational_ptr fBlackLevelTag;
dng_urational fBlackLevelData [kMaxColorPlanes];
public:
dng_raw_preview_tag_set (dng_tiff_directory &directory,
const dng_raw_preview &preview,
const dng_ifd &ifd);
virtual ~dng_raw_preview_tag_set ();
};
/*****************************************************************************/
dng_raw_preview_tag_set::dng_raw_preview_tag_set (dng_tiff_directory &directory,
const dng_raw_preview &preview,
const dng_ifd &ifd)
: dng_preview_tag_set (directory, preview, ifd)
, fOpcodeList2Tag (tcOpcodeList2,
ttUndefined,
0,
NULL)
, fWhiteLevelTag (tcWhiteLevel,
fWhiteLevelData,
preview.SamplesPerPixel ())
, fBlackLevelTag (tcBlackLevel,
fBlackLevelData,
preview.SamplesPerPixel ())
{
if (preview.fOpcodeList2Data.Get ())
{
fOpcodeList2Tag.SetData (preview.fOpcodeList2Data->Buffer ());
fOpcodeList2Tag.SetCount (preview.fOpcodeList2Data->LogicalSize ());
directory.Add (&fOpcodeList2Tag);
}
if (preview.SampleFormat () == sfFloatingPoint)
{
for (uint32 j = 0; j < kMaxColorPlanes; j++)
{
fWhiteLevelData [j] = 32768;
}
directory.Add (&fWhiteLevelTag);
}
else
{
bool nonZeroBlack = false;
for (uint32 j = 0; j < preview.SamplesPerPixel (); j++)
{
fBlackLevelData [j].Set_real64 (preview.fBlackLevel [j], 1);
nonZeroBlack = nonZeroBlack || (preview.fBlackLevel [j] != 0.0);
}
if (nonZeroBlack)
{
directory.Add (&fBlackLevelTag);
}
}
}
/*****************************************************************************/
dng_raw_preview_tag_set::~dng_raw_preview_tag_set ()
{
}
/*****************************************************************************/
dng_raw_preview::dng_raw_preview ()
{
for (uint32 n = 0; n < kMaxColorPlanes; n++)
{
fBlackLevel [n] = 0.0;
}
}
/*****************************************************************************/
void dng_raw_preview::SetIFDInfo (dng_host &host,
const dng_image &image)
{
dng_preview::SetIFDInfo (host, image);
fIFD.fNewSubFileType = sfPreviewImage;
fIFD.fPhotometricInterpretation = piLinearRaw;
if (image.PixelType () == ttFloat)
{
if (fPreferJXL && SupportsJXL (*fImage))
{
fIFD.fCompression = ccJXL;
fIFD.fPredictor = cpNullPredictor;
}
else
{
fIFD.fCompression = ccDeflate;
fIFD.fCompressionQuality = fCompressionQuality;
fIFD.fPredictor = cpFloatingPoint;
}
for (uint32 j = 0; j < fIFD.fSamplesPerPixel; j++)
{
fIFD.fBitsPerSample [j] = 16;
}
fIFD.FindTileSize (512 * 1024);
}
else
{
if (fPreferJXL && SupportsJXL (*fImage))
{
fIFD.fCompression = ccJXL;
fIFD.fPredictor = cpNullPredictor;
}
else
{
fIFD.fCompression = ccLossyJPEG;
fIFD.fCompressionQuality = fCompressionQuality;
}
fIFD.FindTileSize (512 * 512 * fIFD.fSamplesPerPixel);
}
if (fIFD.fCompression == ccJXL)
{
}
}
/*****************************************************************************/
dng_basic_tag_set * dng_raw_preview::AddTagSet (dng_host & /* host */,
dng_tiff_directory &directory) const
{
return new dng_raw_preview_tag_set (directory, *this, fIFD);
}
/*****************************************************************************/
void dng_mask_preview::SetIFDInfo (dng_host &host,
const dng_image &image)
{
dng_preview::SetIFDInfo (host, image);
fIFD.fNewSubFileType = sfPreviewMask;
fIFD.fPhotometricInterpretation = piTransparencyMask;
fIFD.FindTileSize (512 * 512 * fIFD.fSamplesPerPixel);
if (fPreferJXL && SupportsJXL (*fImage))
{
fIFD.fCompression = ccJXL;
fIFD.fPredictor = cpNullPredictor;
}
else
{
fIFD.fCompression = ccDeflate;
fIFD.fPredictor = cpHorizontalDifference;
fIFD.fCompressionQuality = fCompressionQuality;
}
if (fIFD.fCompression == ccJXL)
{
AutoPtr<dng_jxl_encode_settings> settings (host.MakeJXLEncodeSettings (dng_host::use_case_Transparency,
image));
if (fForNegativeCache)
{
settings->SetEffort (1);
}
fIFD.fJXLEncodeSettings.reset (settings.Release ());
fIFD.fJXLDistance = fIFD.fJXLEncodeSettings->Distance ();
fIFD.fJXLEffort = fIFD.fJXLEncodeSettings->Effort ();
fIFD.fJXLDecodeSpeed = fIFD.fJXLEncodeSettings->DecodeSpeed ();
}
}
/*****************************************************************************/
dng_basic_tag_set * dng_mask_preview::AddTagSet (dng_host & /* host */,
dng_tiff_directory &directory) const
{
return new dng_basic_tag_set (directory, fIFD);
}
/*****************************************************************************/
void dng_semantic_mask_preview::SetIFDInfo (dng_host &host,
const dng_image &image)
{
dng_preview::SetIFDInfo (host, image);
fIFD.fNewSubFileType = fOriginalSize ? sfSemanticMask
: sfPreviewSemanticMask;
fIFD.fPhotometricInterpretation = piPhotometricMask;
fIFD.FindTileSize (512 * 512 * fIFD.fSamplesPerPixel);
if (fPreferJXL && SupportsJXL (*fImage))
{
fIFD.fCompression = ccJXL;
fIFD.fPredictor = cpNullPredictor;
}
else
{
fIFD.fCompression = ccDeflate;
fIFD.fPredictor = cpHorizontalDifference;
fIFD.fCompressionQuality = fCompressionQuality;
}
if (fIFD.fCompression == ccJXL)
{
AutoPtr<dng_jxl_encode_settings> settings (host.MakeJXLEncodeSettings (dng_host::use_case_SemanticMask,
image));
if (fForNegativeCache)
{
settings->SetEffort (1);
}
fIFD.fJXLEncodeSettings.reset (settings.Release ());
fIFD.fJXLDistance = fIFD.fJXLEncodeSettings->Distance ();
fIFD.fJXLEffort = fIFD.fJXLEncodeSettings->Effort ();
fIFD.fJXLDecodeSpeed = fIFD.fJXLEncodeSettings->DecodeSpeed ();
}
}
/*****************************************************************************/
dng_basic_tag_set * dng_semantic_mask_preview::AddTagSet (dng_host & /* host */,
dng_tiff_directory &directory) const
{
// Need to write the name and instance ID to the directory, too. Otherwise
// we won't have corresponding labels when reading the image back in.
fTagName.reset (new tag_string (tcSemanticName,
fName,
false));
fTagInstanceID.reset (new tag_string (tcSemanticInstanceID,
fInstanceID,
false));
directory.Add (fTagName.get ());
directory.Add (fTagInstanceID.get ());
// Also need to add the MaskSubArea info.
fTagMaskSubArea.reset (new tag_uint32_ptr (tcMaskSubArea,
& fMaskSubArea [0],
4));
directory.Add (fTagMaskSubArea.get ());
return new dng_basic_tag_set (directory, fIFD);
}
/*****************************************************************************/
void dng_depth_preview::SetIFDInfo (dng_host &host,
const dng_image &image)
{
dng_preview::SetIFDInfo (host, image);
fIFD.fNewSubFileType = fFullResolution ? sfDepthMap
: sfPreviewDepthMap;
fIFD.fPhotometricInterpretation = piDepth;
fIFD.FindTileSize (512 * 512 * fIFD.fSamplesPerPixel);
if (fPreferJXL && SupportsJXL (*fImage))
{
fIFD.fCompression = ccJXL;
fIFD.fPredictor = cpNullPredictor;
}
else
{
fIFD.fCompression = ccDeflate;
fIFD.fPredictor = cpHorizontalDifference;
fIFD.fCompressionQuality = fCompressionQuality;
}
if (fIFD.fCompression == ccJXL)
{
AutoPtr<dng_jxl_encode_settings> settings (host.MakeJXLEncodeSettings (dng_host::use_case_Depth,
image));
if (fForNegativeCache)
{
settings->SetEffort (1);
}
fIFD.fJXLEncodeSettings.reset (settings.Release ());
fIFD.fJXLDistance = fIFD.fJXLEncodeSettings->Distance ();
fIFD.fJXLEffort = fIFD.fJXLEncodeSettings->Effort ();
fIFD.fJXLDecodeSpeed = fIFD.fJXLEncodeSettings->DecodeSpeed ();
}
}
/*****************************************************************************/
dng_basic_tag_set * dng_depth_preview::AddTagSet (dng_host & /* host */,
dng_tiff_directory &directory) const
{
return new dng_basic_tag_set (directory, fIFD);
}
/*****************************************************************************/
void dng_preview_list::Append (AutoPtr<dng_preview> &preview)
{
if (preview.Get ())
{
std::shared_ptr<const dng_preview> entry (preview.Release ());
fPreview.push_back (entry);
}
}
/*****************************************************************************/