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.
3407 lines
64 KiB
3407 lines
64 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_read_image.cpp#7 $ */ |
|
/* $DateTime: 2012/07/31 22:04:34 $ */ |
|
/* $Change: 840853 $ */ |
|
/* $Author: tknoll $ */ |
|
|
|
/*****************************************************************************/ |
|
|
|
#include "dng_read_image.h" |
|
|
|
#include "dng_abort_sniffer.h" |
|
#include "dng_area_task.h" |
|
#include "dng_bottlenecks.h" |
|
#include "dng_exceptions.h" |
|
#include "dng_flags.h" |
|
#include "dng_host.h" |
|
#include "dng_image.h" |
|
#include "dng_ifd.h" |
|
#include "dng_jpeg_image.h" |
|
#include "dng_lossless_jpeg.h" |
|
#include "dng_mutex.h" |
|
#include "dng_memory.h" |
|
#include "dng_pixel_buffer.h" |
|
#include "dng_safe_arithmetic.h" |
|
#include "dng_tag_types.h" |
|
#include "dng_tag_values.h" |
|
#include "dng_utils.h" |
|
|
|
#include "zlib.h" |
|
|
|
#if qDNGUseLibJPEG |
|
#include "dng_jpeg_memory_source.h" |
|
#include "dng_jpeglib.h" |
|
#endif |
|
|
|
#include <limits> |
|
|
|
/******************************************************************************/ |
|
|
|
static void DecodeDelta8 (uint8 *dPtr, |
|
uint32 rows, |
|
uint32 cols, |
|
uint32 channels) |
|
{ |
|
|
|
const uint32 dRowStep = cols * channels; |
|
|
|
for (uint32 row = 0; row < rows; row++) |
|
{ |
|
|
|
for (uint32 col = 1; col < cols; col++) |
|
{ |
|
|
|
for (uint32 channel = 0; channel < channels; channel++) |
|
{ |
|
|
|
dPtr [col * channels + channel] += dPtr [(col - 1) * channels + channel]; |
|
|
|
} |
|
|
|
} |
|
|
|
dPtr += dRowStep; |
|
|
|
} |
|
|
|
} |
|
|
|
/******************************************************************************/ |
|
|
|
static void DecodeDelta16 (uint16 *dPtr, |
|
uint32 rows, |
|
uint32 cols, |
|
uint32 channels) |
|
{ |
|
|
|
const uint32 dRowStep = cols * channels; |
|
|
|
for (uint32 row = 0; row < rows; row++) |
|
{ |
|
|
|
for (uint32 col = 1; col < cols; col++) |
|
{ |
|
|
|
for (uint32 channel = 0; channel < channels; channel++) |
|
{ |
|
|
|
dPtr [col * channels + channel] += dPtr [(col - 1) * channels + channel]; |
|
|
|
} |
|
|
|
} |
|
|
|
dPtr += dRowStep; |
|
|
|
} |
|
|
|
} |
|
|
|
/******************************************************************************/ |
|
|
|
static void DecodeDelta32 (uint32 *dPtr, |
|
uint32 rows, |
|
uint32 cols, |
|
uint32 channels) |
|
{ |
|
|
|
const uint32 dRowStep = cols * channels; |
|
|
|
for (uint32 row = 0; row < rows; row++) |
|
{ |
|
|
|
for (uint32 col = 1; col < cols; col++) |
|
{ |
|
|
|
for (uint32 channel = 0; channel < channels; channel++) |
|
{ |
|
|
|
dPtr [col * channels + channel] += dPtr [(col - 1) * channels + channel]; |
|
|
|
} |
|
|
|
} |
|
|
|
dPtr += dRowStep; |
|
|
|
} |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
inline void DecodeDeltaBytes (uint8 *bytePtr, int32 cols, int32 channels) |
|
{ |
|
|
|
if (channels == 1) |
|
{ |
|
|
|
uint8 b0 = bytePtr [0]; |
|
|
|
bytePtr += 1; |
|
|
|
for (int32 col = 1; col < cols; ++col) |
|
{ |
|
|
|
b0 += bytePtr [0]; |
|
|
|
bytePtr [0] = b0; |
|
|
|
bytePtr += 1; |
|
|
|
} |
|
|
|
} |
|
|
|
else if (channels == 3) |
|
{ |
|
|
|
uint8 b0 = bytePtr [0]; |
|
uint8 b1 = bytePtr [1]; |
|
uint8 b2 = bytePtr [2]; |
|
|
|
bytePtr += 3; |
|
|
|
for (int32 col = 1; col < cols; ++col) |
|
{ |
|
|
|
b0 += bytePtr [0]; |
|
b1 += bytePtr [1]; |
|
b2 += bytePtr [2]; |
|
|
|
bytePtr [0] = b0; |
|
bytePtr [1] = b1; |
|
bytePtr [2] = b2; |
|
|
|
bytePtr += 3; |
|
|
|
} |
|
|
|
} |
|
|
|
else if (channels == 4) |
|
{ |
|
|
|
uint8 b0 = bytePtr [0]; |
|
uint8 b1 = bytePtr [1]; |
|
uint8 b2 = bytePtr [2]; |
|
uint8 b3 = bytePtr [3]; |
|
|
|
bytePtr += 4; |
|
|
|
for (int32 col = 1; col < cols; ++col) |
|
{ |
|
|
|
b0 += bytePtr [0]; |
|
b1 += bytePtr [1]; |
|
b2 += bytePtr [2]; |
|
b3 += bytePtr [3]; |
|
|
|
bytePtr [0] = b0; |
|
bytePtr [1] = b1; |
|
bytePtr [2] = b2; |
|
bytePtr [3] = b3; |
|
|
|
bytePtr += 4; |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
for (int32 col = 1; col < cols; ++col) |
|
{ |
|
|
|
for (int32 chan = 0; chan < channels; ++chan) |
|
{ |
|
|
|
bytePtr [chan + channels] += bytePtr [chan]; |
|
|
|
} |
|
|
|
bytePtr += channels; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
static void DecodeFPDelta (uint8 *input, |
|
uint8 *output, |
|
int32 cols, |
|
int32 channels, |
|
int32 bytesPerSample) |
|
{ |
|
|
|
DecodeDeltaBytes (input, cols * bytesPerSample, channels); |
|
|
|
int32 rowIncrement = cols * channels; |
|
|
|
if (bytesPerSample == 2) |
|
{ |
|
|
|
#if qDNGBigEndian |
|
const uint8 *input0 = input; |
|
const uint8 *input1 = input + rowIncrement; |
|
#else |
|
const uint8 *input1 = input; |
|
const uint8 *input0 = input + rowIncrement; |
|
#endif |
|
|
|
for (int32 col = 0; col < rowIncrement; ++col) |
|
{ |
|
|
|
output [0] = input0 [col]; |
|
output [1] = input1 [col]; |
|
|
|
output += 2; |
|
|
|
} |
|
|
|
} |
|
|
|
else if (bytesPerSample == 3) |
|
{ |
|
|
|
const uint8 *input0 = input; |
|
const uint8 *input1 = input + rowIncrement; |
|
const uint8 *input2 = input + rowIncrement * 2; |
|
|
|
for (int32 col = 0; col < rowIncrement; ++col) |
|
{ |
|
|
|
output [0] = input0 [col]; |
|
output [1] = input1 [col]; |
|
output [2] = input2 [col]; |
|
|
|
output += 3; |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
#if qDNGBigEndian |
|
const uint8 *input0 = input; |
|
const uint8 *input1 = input + rowIncrement; |
|
const uint8 *input2 = input + rowIncrement * 2; |
|
const uint8 *input3 = input + rowIncrement * 3; |
|
#else |
|
const uint8 *input3 = input; |
|
const uint8 *input2 = input + rowIncrement; |
|
const uint8 *input1 = input + rowIncrement * 2; |
|
const uint8 *input0 = input + rowIncrement * 3; |
|
#endif |
|
|
|
for (int32 col = 0; col < rowIncrement; ++col) |
|
{ |
|
|
|
output [0] = input0 [col]; |
|
output [1] = input1 [col]; |
|
output [2] = input2 [col]; |
|
output [3] = input3 [col]; |
|
|
|
output += 4; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
bool DecodePackBits (dng_stream &stream, |
|
uint8 *dPtr, |
|
int32 dstCount) |
|
{ |
|
|
|
while (dstCount > 0) |
|
{ |
|
|
|
int32 runCount = (int8) stream.Get_uint8 (); |
|
|
|
if (runCount >= 0) |
|
{ |
|
|
|
++runCount; |
|
|
|
dstCount -= runCount; |
|
|
|
if (dstCount < 0) |
|
return false; |
|
|
|
stream.Get (dPtr, runCount); |
|
|
|
dPtr += runCount; |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
runCount = -runCount + 1; |
|
|
|
dstCount -= runCount; |
|
|
|
if (dstCount < 0) |
|
return false; |
|
|
|
uint8 x = stream.Get_uint8 (); |
|
|
|
while (runCount--) |
|
{ |
|
|
|
*(dPtr++) = x; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return true; |
|
|
|
} |
|
|
|
/******************************************************************************/ |
|
|
|
class dng_lzw_expander |
|
{ |
|
|
|
private: |
|
|
|
enum |
|
{ |
|
kResetCode = 256, |
|
kEndCode = 257, |
|
kTableSize = 4096 |
|
}; |
|
|
|
struct LZWExpanderNode |
|
{ |
|
int16 prefix; |
|
int16 final; |
|
int16 depth; |
|
int16 fake_for_padding; |
|
}; |
|
|
|
dng_memory_data fBuffer; |
|
|
|
LZWExpanderNode *fTable; |
|
|
|
const uint8 *fSrcPtr; |
|
|
|
int32 fSrcCount; |
|
|
|
int32 fByteOffset; |
|
|
|
uint32 fBitBuffer; |
|
int32 fBitBufferCount; |
|
|
|
int32 fNextCode; |
|
|
|
int32 fCodeSize; |
|
|
|
public: |
|
|
|
dng_lzw_expander (); |
|
|
|
bool Expand (const uint8 *sPtr, |
|
uint8 *dPtr, |
|
int32 sCount, |
|
int32 dCount); |
|
|
|
private: |
|
|
|
void InitTable (); |
|
|
|
void AddTable (int32 w, int32 k); |
|
|
|
bool GetCodeWord (int32 &code); |
|
|
|
// Hidden copy constructor and assignment operator. |
|
|
|
dng_lzw_expander (const dng_lzw_expander &expander); |
|
|
|
dng_lzw_expander & operator= (const dng_lzw_expander &expander); |
|
|
|
}; |
|
|
|
/******************************************************************************/ |
|
|
|
dng_lzw_expander::dng_lzw_expander () |
|
|
|
: fBuffer () |
|
, fTable (NULL) |
|
, fSrcPtr (NULL) |
|
, fSrcCount (0) |
|
, fByteOffset (0) |
|
, fBitBuffer (0) |
|
, fBitBufferCount (0) |
|
, fNextCode (0) |
|
, fCodeSize (0) |
|
|
|
{ |
|
|
|
fBuffer.Allocate (kTableSize * sizeof (LZWExpanderNode)); |
|
|
|
fTable = (LZWExpanderNode *) fBuffer.Buffer (); |
|
|
|
} |
|
|
|
/******************************************************************************/ |
|
|
|
void dng_lzw_expander::InitTable () |
|
{ |
|
|
|
fCodeSize = 9; |
|
|
|
fNextCode = 258; |
|
|
|
LZWExpanderNode *node = &fTable [0]; |
|
|
|
for (int32 code = 0; code < 256; code++) |
|
{ |
|
|
|
node->prefix = -1; |
|
node->final = (int16) code; |
|
node->depth = 1; |
|
|
|
node++; |
|
|
|
} |
|
|
|
} |
|
|
|
/******************************************************************************/ |
|
|
|
void dng_lzw_expander::AddTable (int32 w, int32 k) |
|
{ |
|
|
|
DNG_ASSERT ((w >= 0) && (w <= kTableSize), |
|
"bad w value in dng_lzw_expander::AddTable"); |
|
|
|
LZWExpanderNode *parentNode = &fTable [w]; |
|
|
|
int32 nextCode = fNextCode; |
|
|
|
fNextCode++; |
|
|
|
DNG_ASSERT ((nextCode >= 0) && (nextCode <= kTableSize), |
|
"bad fNextCode value in dng_lzw_expander::AddTable"); |
|
|
|
LZWExpanderNode *node = &fTable [nextCode]; |
|
|
|
node->prefix = (int16) w; |
|
node->final = (int16) k; |
|
node->depth = 1 + parentNode->depth; |
|
|
|
if (nextCode + 1 == (1 << fCodeSize) - 1) |
|
{ |
|
if (fCodeSize != 12) |
|
fCodeSize++; |
|
} |
|
|
|
} |
|
|
|
/******************************************************************************/ |
|
|
|
bool dng_lzw_expander::GetCodeWord (int32 &code) |
|
{ |
|
|
|
// The bit buffer has the current code in the most significant bits, |
|
// so shift off the low orders. |
|
|
|
int32 codeSize = fCodeSize; |
|
|
|
code = fBitBuffer >> (32 - codeSize); |
|
|
|
if (fBitBufferCount >= codeSize) |
|
{ |
|
|
|
// Typical case; get the code from the bit buffer. |
|
|
|
fBitBuffer <<= codeSize; |
|
fBitBufferCount -= codeSize; |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
// The buffer needs to be refreshed. |
|
|
|
const int32 bitsSoFar = fBitBufferCount; |
|
|
|
if (fByteOffset >= fSrcCount) |
|
return false; |
|
|
|
// Buffer a long word |
|
|
|
const uint8 *ptr = fSrcPtr + fByteOffset; |
|
|
|
#if qDNGBigEndian |
|
|
|
fBitBuffer = *((const uint32 *) ptr); |
|
|
|
#else |
|
|
|
{ |
|
|
|
uint32 b0 = ptr [0]; |
|
uint32 b1 = ptr [1]; |
|
uint32 b2 = ptr [2]; |
|
uint32 b3 = ptr [3]; |
|
|
|
fBitBuffer = (((((b0 << 8) | b1) << 8) | b2) << 8) | b3; |
|
|
|
} |
|
|
|
#endif |
|
|
|
fBitBufferCount = 32; |
|
|
|
fByteOffset += 4; |
|
|
|
// Number of additional bits we need |
|
|
|
const int32 bitsUsed = codeSize - bitsSoFar; |
|
|
|
// Number of low order bits in the current buffer we don't care about |
|
|
|
const int32 bitsNotUsed = 32 - bitsUsed; |
|
|
|
code |= fBitBuffer >> bitsNotUsed; |
|
|
|
fBitBuffer <<= bitsUsed; |
|
fBitBufferCount -= bitsUsed; |
|
|
|
} |
|
|
|
return true; |
|
|
|
} |
|
|
|
/******************************************************************************/ |
|
|
|
bool dng_lzw_expander::Expand (const uint8 *sPtr, |
|
uint8 *dPtr, |
|
int32 sCount, |
|
int32 dCount) |
|
{ |
|
|
|
void *dStartPtr = dPtr; |
|
|
|
fSrcPtr = sPtr; |
|
|
|
fSrcCount = sCount; |
|
|
|
fByteOffset = 0; |
|
|
|
/* the master decode loop */ |
|
|
|
while (true) |
|
{ |
|
|
|
InitTable (); |
|
|
|
int32 code; |
|
|
|
do |
|
{ |
|
|
|
if (!GetCodeWord (code)) |
|
return false; |
|
|
|
DNG_ASSERT (code <= fNextCode, |
|
"Unexpected LZW code in dng_lzw_expander::Expand"); |
|
|
|
} |
|
while (code == kResetCode); |
|
|
|
if (code == kEndCode) |
|
return true; |
|
|
|
if (code > kEndCode) |
|
return false; |
|
|
|
int32 oldCode = code; |
|
int32 inChar = code; |
|
|
|
*(dPtr++) = (uint8) code; |
|
|
|
if (--dCount == 0) |
|
return true; |
|
|
|
while (true) |
|
{ |
|
|
|
if (!GetCodeWord (code)) |
|
return false; |
|
|
|
if (code == kResetCode) |
|
break; |
|
|
|
if (code == kEndCode) |
|
return true; |
|
|
|
const int32 inCode = code; |
|
|
|
bool repeatLastPixel = false; |
|
|
|
if (code >= fNextCode) |
|
{ |
|
|
|
// This is either a bad file or our code table is not big enough; we |
|
// are going to repeat the last code seen and attempt to muddle thru. |
|
|
|
code = oldCode; |
|
|
|
repeatLastPixel = true; |
|
|
|
} |
|
|
|
// this can only happen if we hit 2 bad codes in a row |
|
|
|
if (code > fNextCode) |
|
return false; |
|
|
|
const int32 depth = fTable [code].depth; |
|
|
|
if (depth < dCount) |
|
{ |
|
|
|
dCount -= depth; |
|
|
|
dPtr += depth; |
|
|
|
uint8 *ptr = dPtr; |
|
|
|
// give the compiler an extra hint to optimize these as registers |
|
|
|
const LZWExpanderNode *localTable = fTable; |
|
|
|
int32 localCode = code; |
|
|
|
// this is usually the hottest loop in LZW expansion |
|
|
|
while (localCode >= kResetCode) |
|
{ |
|
|
|
if (ptr <= dStartPtr) |
|
return false; // about to trash memory |
|
|
|
const LZWExpanderNode &node = localTable [localCode]; |
|
|
|
uint8 tempFinal = (uint8) node.final; |
|
|
|
localCode = node.prefix; |
|
|
|
// Check for bogus table entry |
|
|
|
if (localCode < 0 || localCode > kTableSize) |
|
return false; |
|
|
|
*(--ptr) = tempFinal; |
|
|
|
} |
|
|
|
code = localCode; |
|
|
|
inChar = localCode; |
|
|
|
if (ptr <= dStartPtr) |
|
return false; // about to trash memory |
|
|
|
*(--ptr) = (uint8) inChar; |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
// There might not be enough room for the full code |
|
// so skip the end of it. |
|
|
|
const int32 skip = depth - dCount; |
|
|
|
for (int32 i = 0; i < skip ; i++) |
|
{ |
|
const LZWExpanderNode &node = fTable [code]; |
|
code = node.prefix; |
|
} |
|
|
|
int32 depthUsed = depth - skip; |
|
|
|
dCount -= depthUsed; |
|
|
|
dPtr += depthUsed; |
|
|
|
uint8 *ptr = dPtr; |
|
|
|
while (code >= 0) |
|
{ |
|
|
|
if (ptr <= dStartPtr) |
|
return false; // about to trash memory |
|
|
|
const LZWExpanderNode &node = fTable [code]; |
|
|
|
*(--ptr) = (uint8) node.final; |
|
|
|
code = node.prefix; |
|
|
|
// Check for bogus table entry |
|
|
|
if (code > kTableSize) |
|
return false; |
|
|
|
} |
|
|
|
return true; |
|
|
|
} |
|
|
|
if (repeatLastPixel) |
|
{ |
|
|
|
*(dPtr++) = (uint8) inChar; |
|
|
|
if (--dCount == 0) |
|
return true; |
|
|
|
} |
|
|
|
if (fNextCode < kTableSize) |
|
{ |
|
|
|
AddTable (oldCode, code); |
|
|
|
} |
|
|
|
oldCode = inCode; |
|
|
|
} |
|
|
|
} |
|
|
|
return false; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
dng_row_interleaved_image::dng_row_interleaved_image (dng_image &image, |
|
uint32 factor) |
|
|
|
: dng_image (image.Bounds (), |
|
image.Planes (), |
|
image.PixelType ()) |
|
|
|
, fImage (image ) |
|
, fFactor (factor) |
|
|
|
{ |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
int32 dng_row_interleaved_image::MapRow (int32 row) const |
|
{ |
|
|
|
uint32 rows = Height (); |
|
|
|
int32 top = Bounds ().t; |
|
|
|
uint32 fieldRow = row - top; |
|
|
|
for (uint32 field = 0; true; field++) |
|
{ |
|
|
|
uint32 fieldRows = (rows - field + fFactor - 1) / fFactor; |
|
|
|
if (fieldRow < fieldRows) |
|
{ |
|
|
|
return fieldRow * fFactor + field + top; |
|
|
|
} |
|
|
|
fieldRow -= fieldRows; |
|
|
|
} |
|
|
|
ThrowProgramError (); |
|
|
|
return 0; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
void dng_row_interleaved_image::DoGet (dng_pixel_buffer &buffer) const |
|
{ |
|
|
|
dng_pixel_buffer tempBuffer (buffer); |
|
|
|
for (int32 row = buffer.fArea.t; row < buffer.fArea.b; row++) |
|
{ |
|
|
|
tempBuffer.fArea.t = MapRow (row); |
|
|
|
tempBuffer.fArea.b = tempBuffer.fArea.t + 1; |
|
|
|
tempBuffer.fData = (void *) buffer.DirtyPixel (row, |
|
buffer.fArea.l, |
|
buffer.fPlane); |
|
|
|
fImage.Get (tempBuffer); |
|
|
|
} |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
void dng_row_interleaved_image::DoPut (const dng_pixel_buffer &buffer) |
|
{ |
|
|
|
dng_pixel_buffer tempBuffer (buffer); |
|
|
|
for (int32 row = buffer.fArea.t; row < buffer.fArea.b; row++) |
|
{ |
|
|
|
tempBuffer.fArea.t = MapRow (row); |
|
|
|
tempBuffer.fArea.b = tempBuffer.fArea.t + 1; |
|
|
|
tempBuffer.fData = (void *) buffer.ConstPixel (row, |
|
buffer.fArea.l, |
|
buffer.fPlane); |
|
|
|
fImage.Put (tempBuffer); |
|
|
|
} |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
static void ReorderSubTileBlocks (dng_host &host, |
|
const dng_ifd &ifd, |
|
dng_pixel_buffer &buffer, |
|
AutoPtr<dng_memory_block> &tempBuffer) |
|
{ |
|
|
|
uint32 tempBufferSize = ComputeBufferSize(buffer.fPixelType, |
|
buffer.fArea.Size(), |
|
buffer.fPlanes, padNone); |
|
|
|
if (!tempBuffer.Get () || tempBuffer->LogicalSize () < tempBufferSize) |
|
{ |
|
|
|
tempBuffer.Reset (host.Allocate (tempBufferSize)); |
|
|
|
} |
|
|
|
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 = (const uint8 *) buffer.fData; |
|
uint8 *d0 = tempBuffer->Buffer_uint8 (); |
|
|
|
for (uint32 rowBlock = 0; rowBlock < rowBlocks; rowBlock++) |
|
{ |
|
|
|
uint8 *d1 = d0; |
|
|
|
for (uint32 colBlock = 0; colBlock < colBlocks; colBlock++) |
|
{ |
|
|
|
uint8 *d2 = d1; |
|
|
|
for (uint32 blockRow = 0; blockRow < blockRows; blockRow++) |
|
{ |
|
|
|
for (uint32 j = 0; j < blockColBytes; j++) |
|
{ |
|
|
|
d2 [j] = s0 [j]; |
|
|
|
} |
|
|
|
s0 += blockColBytes; |
|
|
|
d2 += rowStep; |
|
|
|
} |
|
|
|
d1 += colBlockStep; |
|
|
|
} |
|
|
|
d0 += rowBlockStep; |
|
|
|
} |
|
|
|
// Copy back reordered pixels. |
|
|
|
DoCopyBytes (tempBuffer->Buffer (), |
|
buffer.fData, |
|
tempBufferSize); |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
class dng_image_spooler: public dng_spooler |
|
{ |
|
|
|
private: |
|
|
|
dng_host &fHost; |
|
|
|
const dng_ifd &fIFD; |
|
|
|
dng_image &fImage; |
|
|
|
dng_rect fTileArea; |
|
|
|
uint32 fPlane; |
|
uint32 fPlanes; |
|
|
|
dng_memory_block &fBlock; |
|
|
|
AutoPtr<dng_memory_block> &fSubTileBuffer; |
|
|
|
dng_rect fTileStrip; |
|
|
|
uint8 *fBuffer; |
|
|
|
uint32 fBufferCount; |
|
uint32 fBufferSize; |
|
|
|
public: |
|
|
|
dng_image_spooler (dng_host &host, |
|
const dng_ifd &ifd, |
|
dng_image &image, |
|
const dng_rect &tileArea, |
|
uint32 plane, |
|
uint32 planes, |
|
dng_memory_block &block, |
|
AutoPtr<dng_memory_block> &subTileBuffer); |
|
|
|
virtual ~dng_image_spooler (); |
|
|
|
virtual void Spool (const void *data, |
|
uint32 count); |
|
|
|
private: |
|
|
|
// Hidden copy constructor and assignment operator. |
|
|
|
dng_image_spooler (const dng_image_spooler &spooler); |
|
|
|
dng_image_spooler & operator= (const dng_image_spooler &spooler); |
|
|
|
}; |
|
|
|
/*****************************************************************************/ |
|
|
|
dng_image_spooler::dng_image_spooler (dng_host &host, |
|
const dng_ifd &ifd, |
|
dng_image &image, |
|
const dng_rect &tileArea, |
|
uint32 plane, |
|
uint32 planes, |
|
dng_memory_block &block, |
|
AutoPtr<dng_memory_block> &subTileBuffer) |
|
|
|
: fHost (host) |
|
, fIFD (ifd) |
|
, fImage (image) |
|
, fTileArea (tileArea) |
|
, fPlane (plane) |
|
, fPlanes (planes) |
|
, fBlock (block) |
|
, fSubTileBuffer (subTileBuffer) |
|
|
|
, fTileStrip () |
|
, fBuffer (NULL) |
|
, fBufferCount (0) |
|
, fBufferSize (0) |
|
|
|
{ |
|
|
|
uint32 bytesPerRow = fTileArea.W () * fPlanes * (uint32) sizeof (uint16); |
|
|
|
uint32 stripLength = Pin_uint32 (ifd.fSubTileBlockRows, |
|
fBlock.LogicalSize () / bytesPerRow, |
|
fTileArea.H ()); |
|
|
|
stripLength = stripLength / ifd.fSubTileBlockRows |
|
* ifd.fSubTileBlockRows; |
|
|
|
fTileStrip = fTileArea; |
|
fTileStrip.b = fTileArea.t + stripLength; |
|
|
|
fBuffer = (uint8 *) fBlock.Buffer (); |
|
|
|
fBufferCount = 0; |
|
fBufferSize = bytesPerRow * stripLength; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
dng_image_spooler::~dng_image_spooler () |
|
{ |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
void dng_image_spooler::Spool (const void *data, |
|
uint32 count) |
|
{ |
|
|
|
while (count) |
|
{ |
|
|
|
uint32 block = Min_uint32 (count, fBufferSize - fBufferCount); |
|
|
|
if (block == 0) |
|
{ |
|
return; |
|
} |
|
|
|
DoCopyBytes (data, |
|
fBuffer + fBufferCount, |
|
block); |
|
|
|
data = ((const uint8 *) data) + block; |
|
|
|
count -= block; |
|
|
|
fBufferCount += block; |
|
|
|
if (fBufferCount == fBufferSize) |
|
{ |
|
|
|
fHost.SniffForAbort (); |
|
|
|
dng_pixel_buffer buffer (fTileStrip, fPlane, fPlanes, ttShort, |
|
pcInterleaved, fBuffer); |
|
|
|
if (fIFD.fSubTileBlockRows > 1) |
|
{ |
|
|
|
ReorderSubTileBlocks (fHost, |
|
fIFD, |
|
buffer, |
|
fSubTileBuffer); |
|
|
|
} |
|
|
|
fImage.Put (buffer); |
|
|
|
uint32 stripLength = fTileStrip.H (); |
|
|
|
fTileStrip.t = fTileStrip.b; |
|
|
|
fTileStrip.b = Min_int32 (fTileStrip.t + stripLength, |
|
fTileArea.b); |
|
|
|
fBufferCount = 0; |
|
|
|
fBufferSize = fTileStrip.W () * |
|
fTileStrip.H () * |
|
fPlanes * (uint32) sizeof (uint16); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
dng_read_image::dng_read_image () |
|
|
|
: fJPEGTables () |
|
|
|
{ |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
dng_read_image::~dng_read_image () |
|
{ |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
bool dng_read_image::ReadUncompressed (dng_host &host, |
|
const dng_ifd &ifd, |
|
dng_stream &stream, |
|
dng_image &image, |
|
const dng_rect &tileArea, |
|
uint32 plane, |
|
uint32 planes, |
|
AutoPtr<dng_memory_block> &uncompressedBuffer, |
|
AutoPtr<dng_memory_block> &subTileBlockBuffer) |
|
{ |
|
|
|
uint32 rows = tileArea.H (); |
|
uint32 samplesPerRow = tileArea.W (); |
|
|
|
if (ifd.fPlanarConfiguration == pcRowInterleaved) |
|
{ |
|
rows = SafeUint32Mult(rows, planes); |
|
} |
|
else |
|
{ |
|
samplesPerRow = SafeUint32Mult(samplesPerRow, planes); |
|
} |
|
|
|
uint32 samplesPerTile = SafeUint32Mult(samplesPerRow, rows); |
|
|
|
if (uncompressedBuffer.Get () == NULL) |
|
{ |
|
|
|
#if qDNGValidate |
|
|
|
ReportError ("Fuzz: Missing uncompressed buffer"); |
|
|
|
#endif |
|
|
|
ThrowBadFormat (); |
|
|
|
} |
|
|
|
uint32 bitDepth = ifd.fBitsPerSample [plane]; |
|
|
|
uint32 pixelType = ttUndefined; |
|
|
|
if (bitDepth == 8) |
|
{ |
|
|
|
pixelType = ttByte; |
|
|
|
stream.Get (uncompressedBuffer->Buffer (), samplesPerTile); |
|
|
|
} |
|
|
|
else if (bitDepth == 16 && ifd.fSampleFormat [0] == sfFloatingPoint) |
|
{ |
|
|
|
pixelType = ttFloat; |
|
|
|
uint32 *p_uint32 = (uint32 *) uncompressedBuffer->Buffer (); |
|
|
|
for (uint32 j = 0; j < samplesPerTile; j++) |
|
{ |
|
|
|
p_uint32 [j] = DNG_HalfToFloat (stream.Get_uint16 ()); |
|
|
|
} |
|
|
|
} |
|
|
|
else if (bitDepth == 24 && ifd.fSampleFormat [0] == sfFloatingPoint) |
|
{ |
|
|
|
pixelType = ttFloat; |
|
|
|
uint32 *p_uint32 = (uint32 *) uncompressedBuffer->Buffer (); |
|
|
|
for (uint32 j = 0; j < samplesPerTile; j++) |
|
{ |
|
|
|
uint8 input [3]; |
|
|
|
if (stream.LittleEndian ()) |
|
{ |
|
input [2] = stream.Get_uint8 (); |
|
input [1] = stream.Get_uint8 (); |
|
input [0] = stream.Get_uint8 (); |
|
} |
|
|
|
else |
|
{ |
|
input [0] = stream.Get_uint8 (); |
|
input [1] = stream.Get_uint8 (); |
|
input [2] = stream.Get_uint8 (); |
|
} |
|
|
|
p_uint32 [j] = DNG_FP24ToFloat (input); |
|
|
|
} |
|
|
|
} |
|
|
|
else if (bitDepth == 16) |
|
{ |
|
|
|
pixelType = ttShort; |
|
|
|
stream.Get (uncompressedBuffer->Buffer (), samplesPerTile * 2); |
|
|
|
if (stream.SwapBytes ()) |
|
{ |
|
|
|
DoSwapBytes16 ((uint16 *) uncompressedBuffer->Buffer (), |
|
samplesPerTile); |
|
|
|
} |
|
|
|
} |
|
|
|
else if (bitDepth == 32) |
|
{ |
|
|
|
pixelType = image.PixelType (); |
|
|
|
stream.Get (uncompressedBuffer->Buffer (), samplesPerTile * 4); |
|
|
|
if (stream.SwapBytes ()) |
|
{ |
|
|
|
DoSwapBytes32 ((uint32 *) uncompressedBuffer->Buffer (), |
|
samplesPerTile); |
|
|
|
} |
|
|
|
} |
|
|
|
else if (bitDepth == 12) |
|
{ |
|
|
|
pixelType = ttShort; |
|
|
|
uint16 *p = (uint16 *) uncompressedBuffer->Buffer (); |
|
|
|
uint32 evenSamples = samplesPerRow >> 1; |
|
|
|
for (uint32 row = 0; row < rows; row++) |
|
{ |
|
|
|
for (uint32 j = 0; j < evenSamples; j++) |
|
{ |
|
|
|
uint32 b0 = stream.Get_uint8 (); |
|
uint32 b1 = stream.Get_uint8 (); |
|
uint32 b2 = stream.Get_uint8 (); |
|
|
|
p [0] = (uint16) ((b0 << 4) | (b1 >> 4)); |
|
p [1] = (uint16) (((b1 << 8) | b2) & 0x0FFF); |
|
|
|
p += 2; |
|
|
|
} |
|
|
|
if (samplesPerRow & 1) |
|
{ |
|
|
|
uint32 b0 = stream.Get_uint8 (); |
|
uint32 b1 = stream.Get_uint8 (); |
|
|
|
p [0] = (uint16) ((b0 << 4) | (b1 >> 4)); |
|
|
|
p += 1; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
else if (bitDepth > 8 && bitDepth < 16) |
|
{ |
|
|
|
pixelType = ttShort; |
|
|
|
uint16 *p = (uint16 *) uncompressedBuffer->Buffer (); |
|
|
|
uint32 bitMask = (1 << bitDepth) - 1; |
|
|
|
for (uint32 row = 0; row < rows; row++) |
|
{ |
|
|
|
uint32 bitBuffer = 0; |
|
uint32 bufferBits = 0; |
|
|
|
for (uint32 j = 0; j < samplesPerRow; j++) |
|
{ |
|
|
|
while (bufferBits < bitDepth) |
|
{ |
|
|
|
bitBuffer = (bitBuffer << 8) | stream.Get_uint8 (); |
|
|
|
bufferBits += 8; |
|
|
|
} |
|
|
|
p [j] = (uint16) ((bitBuffer >> (bufferBits - bitDepth)) & bitMask); |
|
|
|
bufferBits -= bitDepth; |
|
|
|
} |
|
|
|
p += samplesPerRow; |
|
|
|
} |
|
|
|
} |
|
|
|
else if (bitDepth > 16 && bitDepth < 32) |
|
{ |
|
|
|
pixelType = ttLong; |
|
|
|
uint32 *p = (uint32 *) uncompressedBuffer->Buffer (); |
|
|
|
uint32 bitMask = ((uint32) 1 << bitDepth) - 1; |
|
|
|
for (uint32 row = 0; row < rows; row++) |
|
{ |
|
|
|
uint64 bitBuffer = 0; |
|
uint32 bufferBits = 0; |
|
|
|
for (uint32 j = 0; j < samplesPerRow; j++) |
|
{ |
|
|
|
while (bufferBits < bitDepth) |
|
{ |
|
|
|
bitBuffer = (bitBuffer << 8) | stream.Get_uint8 (); |
|
|
|
bufferBits += 8; |
|
|
|
} |
|
|
|
p [j] = ((uint32) (bitBuffer >> (bufferBits - bitDepth))) & bitMask; |
|
|
|
bufferBits -= bitDepth; |
|
|
|
} |
|
|
|
p += samplesPerRow; |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
dng_pixel_buffer buffer (tileArea, plane, planes, pixelType, |
|
ifd.fPlanarConfiguration, uncompressedBuffer->Buffer ()); |
|
|
|
if (ifd.fSampleBitShift) |
|
{ |
|
|
|
buffer.ShiftRight (ifd.fSampleBitShift); |
|
|
|
} |
|
|
|
if (ifd.fSubTileBlockRows > 1) |
|
{ |
|
|
|
ReorderSubTileBlocks (host, |
|
ifd, |
|
buffer, |
|
subTileBlockBuffer); |
|
|
|
} |
|
|
|
image.Put (buffer); |
|
|
|
return true; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
#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); |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
#endif |
|
|
|
/*****************************************************************************/ |
|
|
|
void dng_read_image::DecodeLossyJPEG (dng_host &host, |
|
dng_image &image, |
|
const dng_rect &tileArea, |
|
uint32 plane, |
|
uint32 planes, |
|
uint32 /* photometricInterpretation */, |
|
uint32 jpegDataSize, |
|
uint8 *jpegDataInMemory) |
|
{ |
|
|
|
#if qDNGUseLibJPEG |
|
|
|
struct jpeg_decompress_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 decompression context. |
|
|
|
jpeg_create_decompress (&cinfo); |
|
|
|
// Set up the memory data source manager. |
|
|
|
size_t jpegDataSizeAsSizet = 0; |
|
ConvertUnsigned(jpegDataSize, &jpegDataSizeAsSizet); |
|
jpeg_source_mgr memorySource = |
|
CreateJpegMemorySource(jpegDataInMemory, |
|
jpegDataSizeAsSizet); |
|
cinfo.src = &memorySource; |
|
|
|
// Read the JPEG header. |
|
|
|
jpeg_read_header (&cinfo, TRUE); |
|
|
|
// Check header. |
|
|
|
{ |
|
// Number of components may not be negative. |
|
if (cinfo.num_components < 0) |
|
{ |
|
ThrowBadFormat(); |
|
} |
|
|
|
// Convert relevant values from header to uint32. |
|
uint32 imageWidthAsUint32 = 0; |
|
uint32 imageHeightAsUint32 = 0; |
|
uint32 numComponentsAsUint32 = 0; |
|
ConvertUnsigned(cinfo.image_width, &imageWidthAsUint32); |
|
ConvertUnsigned(cinfo.image_height, &imageHeightAsUint32); |
|
// num_components is an int. Casting to unsigned is safe because the |
|
// test above guarantees num_components is not negative. |
|
ConvertUnsigned(static_cast<unsigned>(cinfo.num_components), |
|
&numComponentsAsUint32); |
|
|
|
// Check that dimensions of JPEG correspond to dimensions of tile. |
|
if (imageWidthAsUint32 != tileArea.W () || |
|
imageHeightAsUint32 != tileArea.H () || |
|
numComponentsAsUint32 != planes ) |
|
{ |
|
ThrowBadFormat (); |
|
} |
|
} |
|
|
|
// Start the compression. |
|
|
|
jpeg_start_decompress (&cinfo); |
|
|
|
// Setup a one-scanline size buffer. |
|
|
|
dng_pixel_buffer buffer(tileArea, plane, planes, ttByte, pcInterleaved, |
|
NULL); |
|
buffer.fArea.b = tileArea.t + 1; |
|
|
|
buffer.fDirty = true; |
|
|
|
AutoPtr<dng_memory_block> bufferData (host.Allocate (buffer.fRowStep)); |
|
|
|
buffer.fData = bufferData->Buffer (); |
|
|
|
uint8 *sampArray [1]; |
|
|
|
sampArray [0] = bufferData->Buffer_uint8 (); |
|
|
|
// Read each scanline and save to image. |
|
|
|
while (buffer.fArea.t < tileArea.b) |
|
{ |
|
|
|
jpeg_read_scanlines (&cinfo, sampArray, 1); |
|
|
|
image.Put (buffer); |
|
|
|
buffer.fArea.t = buffer.fArea.b; |
|
buffer.fArea.b = buffer.fArea.t + 1; |
|
|
|
} |
|
|
|
// Cleanup. |
|
|
|
jpeg_finish_decompress (&cinfo); |
|
|
|
jpeg_destroy_decompress (&cinfo); |
|
|
|
} |
|
|
|
catch (...) |
|
{ |
|
|
|
jpeg_destroy_decompress (&cinfo); |
|
|
|
throw; |
|
|
|
} |
|
|
|
#else |
|
|
|
// The dng_sdk does not include a lossy JPEG decoder. Override this |
|
// this method to add lossy JPEG support. |
|
|
|
(void) host; |
|
(void) image; |
|
(void) tileArea; |
|
(void) plane; |
|
(void) planes; |
|
(void) jpegDataSize; |
|
(void) jpegDataInMemory; |
|
|
|
ThrowProgramError ("Missing lossy JPEG decoder"); |
|
|
|
#endif |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
static dng_memory_block * ReadJPEGDataToBlock (dng_host &host, |
|
dng_stream &stream, |
|
dng_memory_block *tablesBlock, |
|
uint64 tileOffset, |
|
uint32 tileByteCount, |
|
bool patchFirstByte) |
|
{ |
|
|
|
// This ensures that the "tileByteCount -= 2" operation below will not wrap |
|
// around. |
|
if (tileByteCount <= 2) |
|
{ |
|
ThrowEndOfFile (); |
|
} |
|
|
|
uint32 tablesByteCount = tablesBlock ? tablesBlock->LogicalSize () : 0; |
|
|
|
// This ensures that the "tablesByteCount -= 2" operation below will not |
|
// wrap around. |
|
if (tablesByteCount && tablesByteCount < 4) |
|
{ |
|
ThrowEndOfFile (); |
|
} |
|
|
|
// The JPEG tables start with a two byte SOI marker, and |
|
// and end with a two byte EOI marker. The JPEG tile |
|
// data also starts with a two byte SOI marker. We can |
|
// convert this combination a normal JPEG stream removing |
|
// the last two bytes of the JPEG tables and the first two |
|
// bytes of the tile data, and then concatenating them. |
|
|
|
if (tablesByteCount) |
|
{ |
|
|
|
// Ensure the "tileOffset += 2" operation below will not wrap around. |
|
if (tileOffset > std::numeric_limits<uint64>::max () - 2) |
|
{ |
|
ThrowEndOfFile(); |
|
} |
|
|
|
tablesByteCount -= 2; |
|
|
|
tileOffset += 2; |
|
tileByteCount -= 2; |
|
|
|
} |
|
|
|
// Allocate buffer. |
|
|
|
AutoPtr<dng_memory_block> buffer (host.Allocate ( |
|
SafeUint32Add(tablesByteCount, tileByteCount))); |
|
|
|
// Read in table. |
|
|
|
if (tablesByteCount) |
|
{ |
|
|
|
DoCopyBytes (tablesBlock->Buffer (), |
|
buffer->Buffer (), |
|
tablesByteCount); |
|
|
|
} |
|
|
|
// Read in tile data. |
|
|
|
stream.SetReadPosition (tileOffset); |
|
|
|
stream.Get (buffer->Buffer_uint8 () + tablesByteCount, tileByteCount); |
|
|
|
// Patch first byte, if required. |
|
|
|
if (patchFirstByte) |
|
{ |
|
|
|
buffer->Buffer_uint8 () [0] = 0xFF; |
|
|
|
} |
|
|
|
// Return buffer. |
|
|
|
return buffer.Release (); |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
bool dng_read_image::ReadBaselineJPEG (dng_host &host, |
|
const dng_ifd &ifd, |
|
dng_stream &stream, |
|
dng_image &image, |
|
const dng_rect &tileArea, |
|
uint32 plane, |
|
uint32 planes, |
|
uint32 tileByteCount, |
|
uint8 *jpegDataInMemory) |
|
{ |
|
|
|
// Setup the data source. |
|
|
|
if (fJPEGTables.Get () || !jpegDataInMemory) |
|
{ |
|
|
|
AutoPtr<dng_memory_block> jpegDataBlock; |
|
|
|
jpegDataBlock.Reset (ReadJPEGDataToBlock (host, |
|
stream, |
|
fJPEGTables.Get (), |
|
stream.Position (), |
|
tileByteCount, |
|
ifd.fPatchFirstJPEGByte)); |
|
|
|
DecodeLossyJPEG (host, |
|
image, |
|
tileArea, |
|
plane, |
|
planes, |
|
ifd.fPhotometricInterpretation, |
|
jpegDataBlock->LogicalSize (), |
|
jpegDataBlock->Buffer_uint8 ()); |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
if (ifd.fPatchFirstJPEGByte && tileByteCount) |
|
{ |
|
jpegDataInMemory [0] = 0xFF; |
|
} |
|
|
|
DecodeLossyJPEG (host, |
|
image, |
|
tileArea, |
|
plane, |
|
planes, |
|
ifd.fPhotometricInterpretation, |
|
tileByteCount, |
|
jpegDataInMemory); |
|
|
|
} |
|
|
|
return true; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
bool dng_read_image::ReadLosslessJPEG (dng_host &host, |
|
const dng_ifd &ifd, |
|
dng_stream &stream, |
|
dng_image &image, |
|
const dng_rect &tileArea, |
|
uint32 plane, |
|
uint32 planes, |
|
uint32 tileByteCount, |
|
AutoPtr<dng_memory_block> &uncompressedBuffer, |
|
AutoPtr<dng_memory_block> &subTileBlockBuffer) |
|
{ |
|
|
|
// If the tile area is empty, there's nothing to read. |
|
if (tileArea.IsEmpty ()) |
|
{ |
|
return true; |
|
} |
|
|
|
uint32 bytesPerRow = SafeUint32Mult (tileArea.W(), planes, |
|
static_cast<uint32> (sizeof (uint16))); |
|
|
|
uint32 rowsPerStrip = Pin_uint32 (ifd.fSubTileBlockRows, |
|
kImageBufferSize / bytesPerRow, |
|
tileArea.H ()); |
|
|
|
rowsPerStrip = rowsPerStrip / ifd.fSubTileBlockRows |
|
* ifd.fSubTileBlockRows; |
|
|
|
uint32 bufferSize = SafeUint32Mult (bytesPerRow, rowsPerStrip); |
|
|
|
if (uncompressedBuffer.Get () && |
|
uncompressedBuffer->LogicalSize () < bufferSize) |
|
{ |
|
|
|
uncompressedBuffer.Reset (); |
|
|
|
} |
|
|
|
if (uncompressedBuffer.Get () == NULL) |
|
{ |
|
|
|
uncompressedBuffer.Reset (host.Allocate (bufferSize)); |
|
|
|
} |
|
|
|
dng_image_spooler spooler (host, |
|
ifd, |
|
image, |
|
tileArea, |
|
plane, |
|
planes, |
|
*uncompressedBuffer.Get (), |
|
subTileBlockBuffer); |
|
|
|
uint32 decodedSize = SafeUint32Mult(tileArea.W (), |
|
tileArea.H (), |
|
planes, (uint32) sizeof (uint16)); |
|
|
|
bool bug16 = ifd.fLosslessJPEGBug16; |
|
|
|
uint64 tileOffset = stream.Position (); |
|
|
|
DecodeLosslessJPEG (stream, |
|
spooler, |
|
decodedSize, |
|
decodedSize, |
|
bug16); |
|
|
|
if (stream.Position () > tileOffset + tileByteCount) |
|
{ |
|
ThrowBadFormat (); |
|
} |
|
|
|
return true; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
bool dng_read_image::CanReadTile (const dng_ifd &ifd) |
|
{ |
|
|
|
if (ifd.fSampleFormat [0] != sfUnsignedInteger && |
|
ifd.fSampleFormat [0] != sfFloatingPoint) |
|
{ |
|
return false; |
|
} |
|
|
|
switch (ifd.fCompression) |
|
{ |
|
|
|
case ccUncompressed: |
|
{ |
|
|
|
if (ifd.fSampleFormat [0] == sfFloatingPoint) |
|
{ |
|
|
|
return (ifd.fBitsPerSample [0] == 16 || |
|
ifd.fBitsPerSample [0] == 24 || |
|
ifd.fBitsPerSample [0] == 32); |
|
|
|
} |
|
|
|
return ifd.fBitsPerSample [0] >= 8 && |
|
ifd.fBitsPerSample [0] <= 32; |
|
|
|
} |
|
|
|
case ccJPEG: |
|
{ |
|
|
|
if (ifd.fSampleFormat [0] != sfUnsignedInteger) |
|
{ |
|
return false; |
|
} |
|
|
|
if (ifd.IsBaselineJPEG ()) |
|
{ |
|
|
|
// Baseline JPEG. |
|
|
|
return true; |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
// Lossless JPEG. |
|
|
|
return ifd.fBitsPerSample [0] >= 8 && |
|
ifd.fBitsPerSample [0] <= 16; |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
case ccLZW: |
|
case ccDeflate: |
|
case ccOldDeflate: |
|
case ccPackBits: |
|
{ |
|
|
|
if (ifd.fSampleFormat [0] == sfFloatingPoint) |
|
{ |
|
|
|
if (ifd.fCompression == ccPackBits) |
|
{ |
|
return false; |
|
} |
|
|
|
if (ifd.fPredictor != cpNullPredictor && |
|
ifd.fPredictor != cpFloatingPoint && |
|
ifd.fPredictor != cpFloatingPointX2 && |
|
ifd.fPredictor != cpFloatingPointX4) |
|
{ |
|
return false; |
|
} |
|
|
|
if (ifd.fBitsPerSample [0] != 16 && |
|
ifd.fBitsPerSample [0] != 24 && |
|
ifd.fBitsPerSample [0] != 32) |
|
{ |
|
return false; |
|
} |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
if (ifd.fPredictor != cpNullPredictor && |
|
ifd.fPredictor != cpHorizontalDifference && |
|
ifd.fPredictor != cpHorizontalDifferenceX2 && |
|
ifd.fPredictor != cpHorizontalDifferenceX4) |
|
{ |
|
return false; |
|
} |
|
|
|
if (ifd.fBitsPerSample [0] != 8 && |
|
ifd.fBitsPerSample [0] != 16 && |
|
ifd.fBitsPerSample [0] != 32) |
|
{ |
|
return false; |
|
} |
|
|
|
} |
|
|
|
return true; |
|
|
|
} |
|
|
|
default: |
|
{ |
|
break; |
|
} |
|
|
|
} |
|
|
|
return false; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
bool dng_read_image::NeedsCompressedBuffer (const dng_ifd &ifd) |
|
{ |
|
|
|
if (ifd.fCompression == ccLZW || |
|
ifd.fCompression == ccDeflate || |
|
ifd.fCompression == ccOldDeflate || |
|
ifd.fCompression == ccPackBits) |
|
{ |
|
return true; |
|
} |
|
|
|
return false; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
void dng_read_image::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_read_image::DecodePredictor (dng_host & /* host */, |
|
const dng_ifd &ifd, |
|
dng_pixel_buffer &buffer) |
|
{ |
|
|
|
switch (ifd.fPredictor) |
|
{ |
|
|
|
case cpNullPredictor: |
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
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: |
|
{ |
|
|
|
DecodeDelta8 ((uint8 *) buffer.fData, |
|
buffer.fArea.H (), |
|
buffer.fArea.W () / xFactor, |
|
buffer.fPlanes * xFactor); |
|
|
|
return; |
|
|
|
} |
|
|
|
case ttShort: |
|
{ |
|
|
|
DecodeDelta16 ((uint16 *) buffer.fData, |
|
buffer.fArea.H (), |
|
buffer.fArea.W () / xFactor, |
|
buffer.fPlanes * xFactor); |
|
|
|
return; |
|
|
|
} |
|
|
|
case ttLong: |
|
{ |
|
|
|
DecodeDelta32 ((uint32 *) buffer.fData, |
|
buffer.fArea.H (), |
|
buffer.fArea.W () / xFactor, |
|
buffer.fPlanes * xFactor); |
|
|
|
return; |
|
|
|
} |
|
|
|
default: |
|
break; |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
default: |
|
break; |
|
|
|
} |
|
|
|
ThrowBadFormat (); |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
void dng_read_image::ReadTile (dng_host &host, |
|
const dng_ifd &ifd, |
|
dng_stream &stream, |
|
dng_image &image, |
|
const dng_rect &tileArea, |
|
uint32 plane, |
|
uint32 planes, |
|
uint32 tileByteCount, |
|
AutoPtr<dng_memory_block> &compressedBuffer, |
|
AutoPtr<dng_memory_block> &uncompressedBuffer, |
|
AutoPtr<dng_memory_block> &subTileBlockBuffer) |
|
{ |
|
|
|
switch (ifd.fCompression) |
|
{ |
|
|
|
case ccLZW: |
|
case ccDeflate: |
|
case ccOldDeflate: |
|
case ccPackBits: |
|
{ |
|
|
|
// Figure out uncompressed size. |
|
|
|
uint32 bytesPerSample = (ifd.fBitsPerSample [0] >> 3); |
|
|
|
uint32 rowStep = 0; |
|
|
|
uint32 sampleCount = 0; |
|
|
|
if (!SafeUint32Mult (planes, tileArea.W (), &rowStep) || |
|
!SafeUint32Mult (rowStep, tileArea.H (), &sampleCount)) |
|
{ |
|
|
|
ThrowMemoryFull ("Arithmetic overflow computing sample count."); |
|
|
|
} |
|
|
|
// Setup pixel buffer to hold uncompressed data. |
|
|
|
uint32 pixelType = ttUndefined; |
|
|
|
if (ifd.fSampleFormat [0] == sfFloatingPoint) |
|
{ |
|
pixelType = ttFloat; |
|
} |
|
|
|
else if (ifd.fBitsPerSample [0] == 8) |
|
{ |
|
pixelType = ttByte; |
|
} |
|
|
|
else if (ifd.fBitsPerSample [0] == 16) |
|
{ |
|
pixelType = ttShort; |
|
} |
|
|
|
else if (ifd.fBitsPerSample [0] == 32) |
|
{ |
|
pixelType = ttLong; |
|
} |
|
|
|
else |
|
{ |
|
ThrowBadFormat (); |
|
} |
|
|
|
uint32 uncompressedSize = ComputeBufferSize (pixelType, tileArea.Size(), |
|
planes, padNone); |
|
|
|
dng_pixel_buffer buffer (tileArea, plane, planes, pixelType, pcInterleaved, |
|
NULL); |
|
|
|
uint32 bufferSize = uncompressedSize; |
|
|
|
// If we are using the floating point predictor, we need an extra |
|
// buffer row. |
|
|
|
if (ifd.fPredictor == cpFloatingPoint || |
|
ifd.fPredictor == cpFloatingPointX2 || |
|
ifd.fPredictor == cpFloatingPointX4) |
|
{ |
|
uint32 rowSize = 0; |
|
if (!SafeUint32Mult (rowStep, buffer.fPixelSize, &rowSize) || |
|
!SafeUint32Add (bufferSize, rowSize, &bufferSize)) |
|
{ |
|
|
|
ThrowMemoryFull ("Arithmetic overflow computing buffer size."); |
|
|
|
} |
|
} |
|
|
|
// If are processing less than full size floating point data, |
|
// we need space to expand the data to full floating point size. |
|
|
|
if (buffer.fPixelType == ttFloat) |
|
{ |
|
bufferSize = Max_uint32 (bufferSize, |
|
SafeUint32Mult(sampleCount, 4)); |
|
} |
|
|
|
// Sometimes with multi-threading and planar image using strips, |
|
// we can process a small tile before a large tile on a thread. |
|
// Simple fix is to just reallocate the buffer if it is too small. |
|
|
|
if (uncompressedBuffer.Get () && |
|
uncompressedBuffer->LogicalSize () < bufferSize) |
|
{ |
|
|
|
uncompressedBuffer.Reset (); |
|
|
|
} |
|
|
|
if (uncompressedBuffer.Get () == NULL) |
|
{ |
|
|
|
uncompressedBuffer.Reset (host.Allocate (bufferSize)); |
|
|
|
} |
|
|
|
buffer.fData = uncompressedBuffer->Buffer (); |
|
|
|
// If using floating point predictor, move buffer pointer to second row. |
|
|
|
if (ifd.fPredictor == cpFloatingPoint || |
|
ifd.fPredictor == cpFloatingPointX2 || |
|
ifd.fPredictor == cpFloatingPointX4) |
|
{ |
|
|
|
buffer.fData = (uint8 *) buffer.fData + |
|
buffer.fRowStep * buffer.fPixelSize; |
|
|
|
} |
|
|
|
// Decompress the data. |
|
|
|
if (ifd.fCompression == ccLZW) |
|
{ |
|
|
|
dng_lzw_expander expander; |
|
|
|
if (!expander.Expand (compressedBuffer->Buffer_uint8 (), |
|
(uint8 *) buffer.fData, |
|
tileByteCount, |
|
uncompressedSize)) |
|
{ |
|
ThrowBadFormat (); |
|
} |
|
|
|
} |
|
|
|
else if (ifd.fCompression == ccPackBits) |
|
{ |
|
|
|
dng_stream subStream (compressedBuffer->Buffer_uint8 (), |
|
tileByteCount); |
|
|
|
if (!DecodePackBits (subStream, |
|
(uint8 *) buffer.fData, |
|
uncompressedSize)) |
|
{ |
|
ThrowBadFormat (); |
|
} |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
uLongf dstLen = uncompressedSize; |
|
|
|
int err = uncompress ((Bytef *) buffer.fData, |
|
&dstLen, |
|
(const Bytef *) compressedBuffer->Buffer (), |
|
tileByteCount); |
|
|
|
if (err != Z_OK) |
|
{ |
|
|
|
if (err == Z_MEM_ERROR) |
|
{ |
|
ThrowMemoryFull (); |
|
} |
|
|
|
else if (err == Z_DATA_ERROR) |
|
{ |
|
// Most other TIFF readers do not fail for this error |
|
// so we should not either, even if it means showing |
|
// a corrupted image to the user. Watson #2530216 |
|
// - tknoll 12/20/11 |
|
} |
|
|
|
else |
|
{ |
|
ThrowBadFormat (); |
|
} |
|
|
|
} |
|
|
|
if (dstLen != uncompressedSize) |
|
{ |
|
ThrowBadFormat (); |
|
} |
|
|
|
} |
|
|
|
// The floating point predictor is byte order independent. |
|
|
|
if (ifd.fPredictor == cpFloatingPoint || |
|
ifd.fPredictor == cpFloatingPointX2 || |
|
ifd.fPredictor == cpFloatingPointX4) |
|
{ |
|
|
|
int32 xFactor = 1; |
|
|
|
if (ifd.fPredictor == cpFloatingPointX2) |
|
{ |
|
xFactor = 2; |
|
} |
|
|
|
else if (ifd.fPredictor == cpFloatingPointX4) |
|
{ |
|
xFactor = 4; |
|
} |
|
|
|
for (int32 row = tileArea.t; row < tileArea.b; row++) |
|
{ |
|
|
|
uint8 *srcPtr = (uint8 *) buffer.DirtyPixel (row , tileArea.l, plane); |
|
// Destination is previous row. |
|
// Subtracting buffer.fRowStep * buffer.fPixelSize will |
|
// always result in a pointer that lies inside the buffer |
|
// because above, we added exactly the same offset to |
|
// buffer.fData (see the piece of code commented "move |
|
// buffer pointer to second row"). |
|
uint8 *dstPtr = srcPtr - |
|
buffer.fRowStep * buffer.fPixelSize; |
|
|
|
DecodeFPDelta (srcPtr, |
|
dstPtr, |
|
tileArea.W () / xFactor, |
|
planes * xFactor, |
|
bytesPerSample); |
|
|
|
} |
|
|
|
buffer.fData = (uint8 *) buffer.fData - |
|
buffer.fRowStep * buffer.fPixelSize; |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
// Both these compression algorithms are byte based. |
|
|
|
if (stream.SwapBytes ()) |
|
{ |
|
|
|
ByteSwapBuffer (host, |
|
buffer); |
|
|
|
} |
|
|
|
// Undo the predictor. |
|
|
|
DecodePredictor (host, |
|
ifd, |
|
buffer); |
|
|
|
} |
|
|
|
// Expand floating point data, if needed. |
|
|
|
if (buffer.fPixelType == ttFloat && buffer.fPixelSize == 2) |
|
{ |
|
|
|
uint16 *srcPtr = (uint16 *) buffer.fData; |
|
uint32 *dstPtr = (uint32 *) buffer.fData; |
|
|
|
for (int32 index = sampleCount - 1; index >= 0; index--) |
|
{ |
|
|
|
dstPtr [index] = DNG_HalfToFloat (srcPtr [index]); |
|
|
|
} |
|
|
|
buffer.fPixelSize = 4; |
|
|
|
} |
|
|
|
else if (buffer.fPixelType == ttFloat && buffer.fPixelSize == 3) |
|
{ |
|
|
|
uint8 *srcPtr = ((uint8 *) buffer.fData) + (sampleCount - 1) * 3; |
|
uint32 *dstPtr = ((uint32 *) buffer.fData) + (sampleCount - 1); |
|
|
|
if (stream.BigEndian () || ifd.fPredictor == cpFloatingPoint || |
|
ifd.fPredictor == cpFloatingPointX2 || |
|
ifd.fPredictor == cpFloatingPointX4) |
|
{ |
|
|
|
for (uint32 index = 0; index < sampleCount; index++) |
|
{ |
|
|
|
*(dstPtr--) = DNG_FP24ToFloat (srcPtr); |
|
|
|
srcPtr -= 3; |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
for (uint32 index = 0; index < sampleCount; index++) |
|
{ |
|
|
|
uint8 input [3]; |
|
|
|
input [2] = srcPtr [0]; |
|
input [1] = srcPtr [1]; |
|
input [0] = srcPtr [2]; |
|
|
|
*(dstPtr--) = DNG_FP24ToFloat (input); |
|
|
|
srcPtr -= 3; |
|
|
|
} |
|
|
|
} |
|
|
|
buffer.fPixelSize = 4; |
|
|
|
} |
|
|
|
// Save the data. |
|
|
|
image.Put (buffer); |
|
|
|
return; |
|
|
|
} |
|
|
|
case ccUncompressed: |
|
{ |
|
|
|
if (ReadUncompressed (host, |
|
ifd, |
|
stream, |
|
image, |
|
tileArea, |
|
plane, |
|
planes, |
|
uncompressedBuffer, |
|
subTileBlockBuffer)) |
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
case ccJPEG: |
|
{ |
|
|
|
if (ifd.IsBaselineJPEG ()) |
|
{ |
|
|
|
// Baseline JPEG. |
|
|
|
if (ReadBaselineJPEG (host, |
|
ifd, |
|
stream, |
|
image, |
|
tileArea, |
|
plane, |
|
planes, |
|
tileByteCount, |
|
compressedBuffer.Get () ? compressedBuffer->Buffer_uint8 () : NULL)) |
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
// Otherwise is should be lossless JPEG. |
|
|
|
if (ReadLosslessJPEG (host, |
|
ifd, |
|
stream, |
|
image, |
|
tileArea, |
|
plane, |
|
planes, |
|
tileByteCount, |
|
uncompressedBuffer, |
|
subTileBlockBuffer)) |
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
case ccLossyJPEG: |
|
{ |
|
|
|
if (ReadBaselineJPEG (host, |
|
ifd, |
|
stream, |
|
image, |
|
tileArea, |
|
plane, |
|
planes, |
|
tileByteCount, |
|
compressedBuffer.Get () ? compressedBuffer->Buffer_uint8 () : NULL)) |
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
default: |
|
break; |
|
|
|
} |
|
|
|
ThrowBadFormat (); |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
bool dng_read_image::CanRead (const dng_ifd &ifd) |
|
{ |
|
|
|
if (ifd.fImageWidth < 1 || |
|
ifd.fImageLength < 1) |
|
{ |
|
return false; |
|
} |
|
|
|
if (ifd.fSamplesPerPixel < 1) |
|
{ |
|
return false; |
|
} |
|
|
|
if (ifd.fBitsPerSample [0] < 1) |
|
{ |
|
return false; |
|
} |
|
|
|
for (uint32 j = 1; j < Min_uint32 (ifd.fSamplesPerPixel, |
|
kMaxSamplesPerPixel); j++) |
|
{ |
|
|
|
if (ifd.fBitsPerSample [j] != |
|
ifd.fBitsPerSample [0]) |
|
{ |
|
return false; |
|
} |
|
|
|
if (ifd.fSampleFormat [j] != |
|
ifd.fSampleFormat [0]) |
|
{ |
|
return false; |
|
} |
|
|
|
} |
|
|
|
if ((ifd.fPlanarConfiguration != pcInterleaved ) && |
|
(ifd.fPlanarConfiguration != pcPlanar ) && |
|
(ifd.fPlanarConfiguration != pcRowInterleaved)) |
|
{ |
|
return false; |
|
} |
|
|
|
if (ifd.fUsesStrips == ifd.fUsesTiles) |
|
{ |
|
return false; |
|
} |
|
|
|
uint32 tileCount = ifd.TilesPerImage (); |
|
|
|
if (tileCount < 1) |
|
{ |
|
return false; |
|
} |
|
|
|
bool needTileByteCounts = (ifd.TileByteCount (ifd.TileArea (0, 0)) == 0); |
|
|
|
if (tileCount == 1) |
|
{ |
|
|
|
if (needTileByteCounts) |
|
{ |
|
|
|
if (ifd.fTileByteCount [0] < 1) |
|
{ |
|
return false; |
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
if (ifd.fTileOffsetsCount != tileCount) |
|
{ |
|
return false; |
|
} |
|
|
|
if (needTileByteCounts) |
|
{ |
|
|
|
if (ifd.fTileByteCountsCount != tileCount) |
|
{ |
|
return false; |
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if (!CanReadTile (ifd)) |
|
{ |
|
return false; |
|
} |
|
|
|
return true; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
class dng_read_tiles_task : public dng_area_task |
|
{ |
|
|
|
private: |
|
|
|
dng_read_image &fReadImage; |
|
|
|
dng_host &fHost; |
|
|
|
const dng_ifd &fIFD; |
|
|
|
dng_stream &fStream; |
|
|
|
dng_image &fImage; |
|
|
|
dng_jpeg_image *fJPEGImage; |
|
|
|
dng_fingerprint *fJPEGTileDigest; |
|
|
|
uint32 fOuterSamples; |
|
|
|
uint32 fInnerSamples; |
|
|
|
uint32 fTilesDown; |
|
|
|
uint32 fTilesAcross; |
|
|
|
uint64 *fTileOffset; |
|
|
|
uint32 *fTileByteCount; |
|
|
|
uint32 fCompressedSize; |
|
|
|
uint32 fUncompressedSize; |
|
|
|
dng_mutex fMutex; |
|
|
|
uint32 fNextTileIndex; |
|
|
|
public: |
|
|
|
dng_read_tiles_task (dng_read_image &readImage, |
|
dng_host &host, |
|
const dng_ifd &ifd, |
|
dng_stream &stream, |
|
dng_image &image, |
|
dng_jpeg_image *jpegImage, |
|
dng_fingerprint *jpegTileDigest, |
|
uint32 outerSamples, |
|
uint32 innerSamples, |
|
uint32 tilesDown, |
|
uint32 tilesAcross, |
|
uint64 *tileOffset, |
|
uint32 *tileByteCount, |
|
uint32 compressedSize, |
|
uint32 uncompressedSize) |
|
|
|
: fReadImage (readImage) |
|
, fHost (host) |
|
, fIFD (ifd) |
|
, fStream (stream) |
|
, fImage (image) |
|
, fJPEGImage (jpegImage) |
|
, fJPEGTileDigest (jpegTileDigest) |
|
, fOuterSamples (outerSamples) |
|
, fInnerSamples (innerSamples) |
|
, fTilesDown (tilesDown) |
|
, fTilesAcross (tilesAcross) |
|
, fTileOffset (tileOffset) |
|
, fTileByteCount (tileByteCount) |
|
, fCompressedSize (compressedSize) |
|
, fUncompressedSize (uncompressedSize) |
|
, fMutex ("dng_read_tiles_task") |
|
, fNextTileIndex (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) |
|
{ |
|
|
|
AutoPtr<dng_memory_block> compressedBuffer; |
|
AutoPtr<dng_memory_block> uncompressedBuffer; |
|
AutoPtr<dng_memory_block> subTileBlockBuffer; |
|
|
|
if (!fJPEGImage) |
|
{ |
|
compressedBuffer.Reset (fHost.Allocate (fCompressedSize)); |
|
} |
|
|
|
if (fUncompressedSize) |
|
{ |
|
uncompressedBuffer.Reset (fHost.Allocate (fUncompressedSize)); |
|
} |
|
|
|
while (true) |
|
{ |
|
|
|
uint32 tileIndex; |
|
uint32 byteCount; |
|
|
|
{ |
|
|
|
dng_lock_mutex lock (&fMutex); |
|
|
|
if (fNextTileIndex == fOuterSamples * fTilesDown * fTilesAcross) |
|
{ |
|
return; |
|
} |
|
|
|
tileIndex = fNextTileIndex++; |
|
|
|
TempStreamSniffer noSniffer (fStream, NULL); |
|
|
|
fStream.SetReadPosition (fTileOffset [tileIndex]); |
|
|
|
byteCount = fTileByteCount [tileIndex]; |
|
|
|
if (fJPEGImage) |
|
{ |
|
|
|
fJPEGImage->fJPEGData [tileIndex] . Reset (fHost.Allocate (byteCount)); |
|
|
|
} |
|
|
|
fStream.Get (fJPEGImage ? fJPEGImage->fJPEGData [tileIndex]->Buffer () |
|
: compressedBuffer->Buffer (), |
|
byteCount); |
|
|
|
} |
|
|
|
dng_abort_sniffer::SniffForAbort (sniffer); |
|
|
|
if (fJPEGTileDigest) |
|
{ |
|
|
|
dng_md5_printer printer; |
|
|
|
printer.Process (compressedBuffer->Buffer (), |
|
byteCount); |
|
|
|
fJPEGTileDigest [tileIndex] = printer.Result (); |
|
|
|
} |
|
|
|
dng_stream tileStream (fJPEGImage ? fJPEGImage->fJPEGData [tileIndex]->Buffer () |
|
: compressedBuffer->Buffer (), |
|
byteCount); |
|
|
|
tileStream.SetLittleEndian (fStream.LittleEndian ()); |
|
|
|
uint32 plane = tileIndex / (fTilesDown * fTilesAcross); |
|
|
|
uint32 rowIndex = (tileIndex - plane * fTilesDown * fTilesAcross) / fTilesAcross; |
|
|
|
uint32 colIndex = tileIndex - (plane * fTilesDown + rowIndex) * fTilesAcross; |
|
|
|
dng_rect tileArea = fIFD.TileArea (rowIndex, colIndex); |
|
|
|
dng_host host (&fHost.Allocator (), |
|
sniffer); // Cannot use sniffer attached to main host |
|
|
|
fReadImage.ReadTile (host, |
|
fIFD, |
|
tileStream, |
|
fImage, |
|
tileArea, |
|
plane, |
|
fInnerSamples, |
|
byteCount, |
|
fJPEGImage ? fJPEGImage->fJPEGData [tileIndex] |
|
: compressedBuffer, |
|
uncompressedBuffer, |
|
subTileBlockBuffer); |
|
|
|
} |
|
|
|
} |
|
|
|
private: |
|
|
|
// Hidden copy constructor and assignment operator. |
|
|
|
dng_read_tiles_task (const dng_read_tiles_task &); |
|
|
|
dng_read_tiles_task & operator= (const dng_read_tiles_task &); |
|
|
|
}; |
|
|
|
/*****************************************************************************/ |
|
|
|
void dng_read_image::Read (dng_host &host, |
|
const dng_ifd &ifd, |
|
dng_stream &stream, |
|
dng_image &image, |
|
dng_jpeg_image *jpegImage, |
|
dng_fingerprint *jpegDigest) |
|
{ |
|
|
|
uint32 tileIndex; |
|
|
|
// 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 (image, |
|
ifd.fRowInterleaveFactor); |
|
|
|
Read (host, |
|
tempIFD, |
|
stream, |
|
tempImage, |
|
jpegImage, |
|
jpegDigest); |
|
|
|
return; |
|
|
|
} |
|
|
|
// Figure out inner and outer samples. |
|
|
|
uint32 innerSamples = 1; |
|
uint32 outerSamples = 1; |
|
|
|
if (ifd.fPlanarConfiguration == pcPlanar) |
|
{ |
|
outerSamples = ifd.fSamplesPerPixel; |
|
} |
|
else |
|
{ |
|
innerSamples = ifd.fSamplesPerPixel; |
|
} |
|
|
|
// Calculate number of tiles to read. |
|
|
|
uint32 tilesAcross = ifd.TilesAcross (); |
|
uint32 tilesDown = ifd.TilesDown (); |
|
|
|
uint32 tileCount = SafeUint32Mult (tilesAcross, tilesDown, outerSamples); |
|
|
|
// Find the tile offsets. |
|
|
|
dng_memory_data tileOffsetData (tileCount, sizeof (uint64)); |
|
|
|
uint64 *tileOffset = tileOffsetData.Buffer_uint64 (); |
|
|
|
if (tileCount <= dng_ifd::kMaxTileInfo) |
|
{ |
|
|
|
for (tileIndex = 0; tileIndex < tileCount; tileIndex++) |
|
{ |
|
|
|
tileOffset [tileIndex] = ifd.fTileOffset [tileIndex]; |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
stream.SetReadPosition (ifd.fTileOffsetsOffset); |
|
|
|
for (tileIndex = 0; tileIndex < tileCount; tileIndex++) |
|
{ |
|
|
|
tileOffset [tileIndex] = stream.TagValue_uint32 (ifd.fTileOffsetsType); |
|
|
|
} |
|
|
|
} |
|
|
|
// Quick validity check on tile offsets. |
|
|
|
for (tileIndex = 0; tileIndex < tileCount; tileIndex++) |
|
{ |
|
|
|
#if qDNGValidate |
|
|
|
if (tileOffset [tileIndex] < 8) |
|
{ |
|
|
|
ReportWarning ("Tile/Strip offset less than 8"); |
|
|
|
} |
|
|
|
#endif |
|
|
|
if (tileOffset [tileIndex] >= stream.Length ()) |
|
{ |
|
|
|
ThrowBadFormat (); |
|
|
|
} |
|
|
|
} |
|
|
|
// Buffer to hold the tile byte counts, if needed. |
|
|
|
dng_memory_data tileByteCountData; |
|
|
|
uint32 *tileByteCount = NULL; |
|
|
|
// If we can compute the number of bytes needed to store the |
|
// data, we can split the read for each tile into sub-tiles. |
|
|
|
uint32 uncompressedSize = 0; |
|
|
|
uint32 subTileLength = ifd.fTileLength; |
|
|
|
if (ifd.TileByteCount (ifd.TileArea (0, 0)) != 0) |
|
{ |
|
|
|
uint32 bytesPerPixel = TagTypeSize (ifd.PixelType ()); |
|
|
|
uint32 bytesPerRow = SafeUint32Mult (ifd.fTileWidth, innerSamples, |
|
bytesPerPixel); |
|
|
|
subTileLength = Pin_uint32 (ifd.fSubTileBlockRows, |
|
kImageBufferSize / bytesPerRow, |
|
ifd.fTileLength); |
|
|
|
subTileLength = subTileLength / ifd.fSubTileBlockRows |
|
* ifd.fSubTileBlockRows; |
|
|
|
uncompressedSize = SafeUint32Mult (subTileLength, bytesPerRow); |
|
|
|
} |
|
|
|
// Else we need to know the byte counts. |
|
|
|
else |
|
{ |
|
|
|
tileByteCountData.Allocate (tileCount, sizeof (uint32)); |
|
|
|
tileByteCount = tileByteCountData.Buffer_uint32 (); |
|
|
|
if (tileCount <= dng_ifd::kMaxTileInfo) |
|
{ |
|
|
|
for (tileIndex = 0; tileIndex < tileCount; tileIndex++) |
|
{ |
|
|
|
tileByteCount [tileIndex] = ifd.fTileByteCount [tileIndex]; |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
stream.SetReadPosition (ifd.fTileByteCountsOffset); |
|
|
|
for (tileIndex = 0; tileIndex < tileCount; tileIndex++) |
|
{ |
|
|
|
tileByteCount [tileIndex] = stream.TagValue_uint32 (ifd.fTileByteCountsType); |
|
|
|
} |
|
|
|
} |
|
|
|
// Quick validity check on tile byte counts. |
|
|
|
for (tileIndex = 0; tileIndex < tileCount; tileIndex++) |
|
{ |
|
|
|
if (tileByteCount [tileIndex] < 1 || |
|
tileByteCount [tileIndex] > stream.Length ()) |
|
{ |
|
|
|
ThrowBadFormat (); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// Find maximum tile size, if possible. |
|
|
|
uint32 maxTileByteCount = 0; |
|
|
|
if (tileByteCount) |
|
{ |
|
|
|
for (tileIndex = 0; tileIndex < tileCount; tileIndex++) |
|
{ |
|
|
|
maxTileByteCount = Max_uint32 (maxTileByteCount, |
|
tileByteCount [tileIndex]); |
|
|
|
} |
|
|
|
} |
|
|
|
// Do we need a compressed data buffer? |
|
|
|
uint32 compressedSize = 0; |
|
|
|
bool needsCompressedBuffer = NeedsCompressedBuffer (ifd); |
|
|
|
if (needsCompressedBuffer) |
|
{ |
|
|
|
if (!tileByteCount) |
|
{ |
|
ThrowBadFormat (); |
|
} |
|
|
|
compressedSize = maxTileByteCount; |
|
|
|
} |
|
|
|
// Are we keeping the compressed JPEG image data? |
|
|
|
if (jpegImage) |
|
{ |
|
|
|
if (ifd.IsBaselineJPEG ()) |
|
{ |
|
|
|
jpegImage->fImageSize.h = ifd.fImageWidth; |
|
jpegImage->fImageSize.v = ifd.fImageLength; |
|
|
|
jpegImage->fTileSize.h = ifd.fTileWidth; |
|
jpegImage->fTileSize.v = ifd.fTileLength; |
|
|
|
jpegImage->fUsesStrips = ifd.fUsesStrips; |
|
|
|
jpegImage->fJPEGData.Reset (tileCount); |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
jpegImage = NULL; |
|
|
|
} |
|
|
|
} |
|
|
|
// Do we need to read the JPEG tables? |
|
|
|
if (ifd.fJPEGTablesOffset && ifd.fJPEGTablesCount) |
|
{ |
|
|
|
if (ifd.IsBaselineJPEG ()) |
|
{ |
|
|
|
fJPEGTables.Reset (host.Allocate (ifd.fJPEGTablesCount)); |
|
|
|
stream.SetReadPosition (ifd.fJPEGTablesOffset); |
|
|
|
stream.Get (fJPEGTables->Buffer (), |
|
fJPEGTables->LogicalSize ()); |
|
|
|
} |
|
|
|
} |
|
|
|
AutoArray<dng_fingerprint> jpegTileDigest; |
|
|
|
if (jpegDigest) |
|
{ |
|
|
|
jpegTileDigest.Reset ( |
|
SafeUint32Add(tileCount, (fJPEGTables.Get () ? 1 : 0))); |
|
|
|
} |
|
|
|
// Don't read planes we are not actually saving. |
|
|
|
outerSamples = Min_uint32 (image.Planes (), outerSamples); |
|
|
|
// See if we can do this read using multiple threads. |
|
|
|
bool useMultipleThreads = (outerSamples * tilesDown * tilesAcross >= 2) && |
|
(host.PerformAreaTaskThreads () > 1) && |
|
(maxTileByteCount > 0 && maxTileByteCount <= 1024 * 1024) && |
|
(subTileLength == ifd.fTileLength) && |
|
(ifd.fCompression != ccUncompressed); |
|
|
|
#if qImagecore |
|
useMultipleThreads = false; |
|
#endif |
|
|
|
if (useMultipleThreads) |
|
{ |
|
|
|
uint32 threadCount = Min_uint32 (outerSamples * tilesDown * tilesAcross, |
|
host.PerformAreaTaskThreads ()); |
|
|
|
dng_read_tiles_task task (*this, |
|
host, |
|
ifd, |
|
stream, |
|
image, |
|
jpegImage, |
|
jpegTileDigest.Get (), |
|
outerSamples, |
|
innerSamples, |
|
tilesDown, |
|
tilesAcross, |
|
tileOffset, |
|
tileByteCount, |
|
maxTileByteCount, |
|
uncompressedSize); |
|
|
|
host.PerformAreaTask (task, |
|
dng_rect (0, 0, 16, 16 * threadCount)); |
|
|
|
} |
|
|
|
// Else use a single thread to read all the tiles. |
|
|
|
else |
|
{ |
|
|
|
AutoPtr<dng_memory_block> compressedBuffer; |
|
AutoPtr<dng_memory_block> uncompressedBuffer; |
|
AutoPtr<dng_memory_block> subTileBlockBuffer; |
|
|
|
if (uncompressedSize) |
|
{ |
|
uncompressedBuffer.Reset (host.Allocate (uncompressedSize)); |
|
} |
|
|
|
if (compressedSize && !jpegImage) |
|
{ |
|
compressedBuffer.Reset (host.Allocate (compressedSize)); |
|
} |
|
|
|
else if (jpegDigest) |
|
{ |
|
compressedBuffer.Reset (host.Allocate (maxTileByteCount)); |
|
} |
|
|
|
tileIndex = 0; |
|
|
|
for (uint32 plane = 0; plane < outerSamples; plane++) |
|
{ |
|
|
|
for (uint32 rowIndex = 0; rowIndex < tilesDown; rowIndex++) |
|
{ |
|
|
|
for (uint32 colIndex = 0; colIndex < tilesAcross; colIndex++) |
|
{ |
|
|
|
stream.SetReadPosition (tileOffset [tileIndex]); |
|
|
|
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); |
|
|
|
uint32 subByteCount; |
|
|
|
if (tileByteCount) |
|
{ |
|
subByteCount = tileByteCount [tileIndex]; |
|
} |
|
else |
|
{ |
|
subByteCount = ifd.TileByteCount (subArea); |
|
} |
|
|
|
if (jpegImage) |
|
{ |
|
|
|
jpegImage->fJPEGData [tileIndex].Reset (host.Allocate (subByteCount)); |
|
|
|
stream.Get (jpegImage->fJPEGData [tileIndex]->Buffer (), subByteCount); |
|
|
|
stream.SetReadPosition (tileOffset [tileIndex]); |
|
|
|
} |
|
|
|
else if ((needsCompressedBuffer || jpegDigest) && subByteCount) |
|
{ |
|
|
|
stream.Get (compressedBuffer->Buffer (), subByteCount); |
|
|
|
if (jpegDigest) |
|
{ |
|
|
|
dng_md5_printer printer; |
|
|
|
printer.Process (compressedBuffer->Buffer (), |
|
subByteCount); |
|
|
|
jpegTileDigest [tileIndex] = printer.Result (); |
|
|
|
} |
|
|
|
} |
|
|
|
ReadTile (host, |
|
ifd, |
|
stream, |
|
image, |
|
subArea, |
|
plane, |
|
innerSamples, |
|
subByteCount, |
|
jpegImage ? jpegImage->fJPEGData [tileIndex] : compressedBuffer, |
|
uncompressedBuffer, |
|
subTileBlockBuffer); |
|
|
|
} |
|
|
|
tileIndex++; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// Finish up JPEG digest computation, if needed. |
|
|
|
if (jpegDigest) |
|
{ |
|
|
|
if (fJPEGTables.Get ()) |
|
{ |
|
|
|
dng_md5_printer printer; |
|
|
|
printer.Process (fJPEGTables->Buffer (), |
|
fJPEGTables->LogicalSize ()); |
|
|
|
jpegTileDigest [tileCount] = printer.Result (); |
|
|
|
} |
|
|
|
dng_md5_printer printer2; |
|
|
|
for (uint32 j = 0; j < tileCount + (fJPEGTables.Get () ? 1 : 0); j++) |
|
{ |
|
|
|
printer2.Process (jpegTileDigest [j].data, |
|
dng_fingerprint::kDNGFingerprintSize); |
|
|
|
} |
|
|
|
*jpegDigest = printer2.Result (); |
|
|
|
} |
|
|
|
// Keep the JPEG table in the jpeg image, if any. |
|
|
|
if (jpegImage) |
|
{ |
|
|
|
jpegImage->fJPEGTables.Reset (fJPEGTables.Release ()); |
|
|
|
} |
|
|
|
} |
|
|
|
/*****************************************************************************/
|
|
|