WindowsXP-SP1/windows/advcore/gdiplus/engine/imaging/jpeg/exif.cpp
2020-09-30 16:53:49 +02:00

5564 lines
168 KiB
C++

/**************************************************************************\
*
* Copyright (c) 1998 Microsoft Corporation
*
* Module Name:
*
* exif.cpp
*
* Abstract:
*
* Read the exif properties from an APP1 header
*
* Revision History:
*
* 7/13/1999 OriG
* Created it. Based on code by RickTu.
*
* 7/31/2000 MinLiu took over. 90% of the old code are gone, replaced with
* a new Image property concept based code
*
\**************************************************************************/
#include "precomp.hpp"
#include "jpgcodec.hpp"
#include "propertyutil.hpp"
#include "appproc.hpp"
/**************************************************************************\
*
* Function Description:
*
* Swaps an IFD tag
*
* Arguments:
*
* IFD_TAG -- a pointer to the IFD tag
*
* Return Value:
*
* IFD_TAG
*
\**************************************************************************/
IFD_TAG
SwapIFD_TAG(
IFD_TAG UNALIGNED * pTag
)
{
IFD_TAG tNewTag;
tNewTag.wTag = SWAP_WORD(pTag->wTag);
tNewTag.wType = SWAP_WORD(pTag->wType);
tNewTag.dwCount= SWAP_DWORD(pTag->dwCount);
if (tNewTag.dwCount == 1)
{
switch (tNewTag.wType)
{
case TAG_TYPE_BYTE:
tNewTag.b = pTag->b;
break;
case TAG_TYPE_SHORT:
tNewTag.us = SWAP_WORD(pTag->us);
break;
default:
// This swap will cover all of our cases.
tNewTag.dwOffset = SWAP_DWORD(pTag->dwOffset);
break;
}
}
else
{
// This swap will cover all of our cases.
tNewTag.dwOffset = SWAP_DWORD(pTag->dwOffset);
}
return tNewTag;
}// SwapIFD_TAG()
/**************************************************************************\
*
* Function Description:
*
* Creates thumbnail from EXIF image
*
* Arguments:
*
* thumbImage - The thumbnail extracted from the APP1 header
* lpBase -- A pointer to the beginning of the APP1 header
* count -- The length of the APP1 header
* pTag -- A pointer to the current IFD tag
* pdwThumbnailOffset -- The offset of the thumbnail in the APP1 header
* pdwThumbnailLength -- The length of the thumbnail data
*
* Return Value:
*
* Status code
*
\**************************************************************************/
HRESULT
DecodeThumbnailTags(
OUT IImage **thumbImage,
IN LPBYTE lpBase,
IN INT count,
IN IFD_TAG UNALIGNED * pTag,
IN OUT DWORD *pdwThumbnailOffset,
IN OUT DWORD *pdwThumbnailLength
)
{
HRESULT hresult;
if (*thumbImage)
{
WARNING(("DecodeThumbnailTag called when thumbnail already created"));
return S_OK;
}
switch (pTag->wTag) {
case TAG_JPEG_INTER_FORMAT:
if ( (pTag->wType != TAG_TYPE_LONG) || (pTag->dwCount != 1) )
{
WARNING(("JPEGInterchangeFormat unit found, invalid format."));
}
else
{
*pdwThumbnailOffset = pTag->dwOffset;
}
break;
case TAG_JPEG_INTER_LENGTH:
if ( (pTag->wType != TAG_TYPE_LONG) || (pTag->dwCount != 1) )
{
WARNING(("JPEGInterchangeLength unit found, invalid format."));
}
else
{
*pdwThumbnailLength = pTag->dwOffset;
}
break;
default:
WARNING(("TAG: Invalid Thumbnail Tag"));
break;
}
if (*pdwThumbnailOffset && *pdwThumbnailLength)
{
if ((*pdwThumbnailOffset + *pdwThumbnailLength) > ((DWORD) count))
{
WARNING(("Thumbnail found outside boundary of APP1 header"));
return E_FAIL;
}
#if PROFILE_MEMORY_USAGE
MC_LogAllocation(*pdwThumbnailLength);
#endif
PVOID thumbnailBits = CoTaskMemAlloc(*pdwThumbnailLength);
if (!thumbnailBits)
{
WARNING(("DecodeThumbnailTags: out of memory"));
return E_OUTOFMEMORY;
}
GpMemcpy(thumbnailBits,
(PVOID) (lpBase + *pdwThumbnailOffset),
*pdwThumbnailLength);
GpImagingFactory imgFact;
hresult = imgFact.CreateImageFromBuffer(thumbnailBits,
*pdwThumbnailLength,
DISPOSAL_COTASKMEMFREE,
thumbImage);
if (FAILED(hresult))
{
// If image creation succeeded, thumbnailBits will be freed by
// the IImage destructor
CoTaskMemFree(thumbnailBits);
}
}
return S_OK;
}
/**************************************************************************\
*
* Function Description:
*
* Decodes the EXIF app1 header
*
* Arguments:
*
* propStgImg -- The property storage to modify
* thumbImage -- The thumbnail extracted from the APP1 header
* lpStart -- A pointer to the beginning of the APP1 header
* count -- The length of the APP1 header
*
* Return Value:
*
* Status code
*
\**************************************************************************/
HRESULT
DecodeApp1(
IImage **thumbImage,
LPBYTE lpStart,
INT count
)
{
DWORD dwThumbnailOffset = 0;
DWORD dwThumbnailLength = 0;
LPBYTE lpData = NULL;
DWORD offset = 0;
WORD wEntries = 0;
//
// Decipher data
//
if (count < 8)
{
//
// Make sure the buffer is big enough
//
return E_FAIL;
}
BOOL bBigEndian = (*(WORD UNALIGNED *)(lpStart) == 0x4D4D);
offset = *(DWORD UNALIGNED *)(lpStart + 4);
if (bBigEndian)
offset = SWAP_DWORD(offset);
lpData = (lpStart + offset);
//
// Loop through IFD's
//
do
{
//
// Get number of entries
//
if ((INT) (lpData - lpStart) > (count + (INT) sizeof(WORD)))
{
//
// Buffer too small
//
return E_FAIL;
}
wEntries = *(WORD UNALIGNED*)lpData;
if (bBigEndian)
wEntries = SWAP_WORD(wEntries);
lpData += sizeof(WORD);
// Loop through entries
if (((INT)(lpData - lpStart) + ((INT)wEntries * (INT)sizeof(IFD_TAG)))
> (INT)count )
{
// Buffer too small
return E_FAIL;
}
IFD_TAG UNALIGNED * pTag = (IFD_TAG UNALIGNED *)lpData;
for (INT i = 0; i < wEntries; i++)
{
pTag = ((IFD_TAG UNALIGNED*)lpData) + i;
IFD_TAG tNewTag;
if (bBigEndian) {
tNewTag = SwapIFD_TAG(pTag);
pTag = &tNewTag;
}
// Extract thumbnail
switch (pTag->wTag)
{
case TAG_JPEG_INTER_FORMAT:
case TAG_JPEG_INTER_LENGTH:
DecodeThumbnailTags(thumbImage, lpStart, count, pTag,
&dwThumbnailOffset, &dwThumbnailLength);
break;
case TAG_COMPRESSION:
// Hit thumbnail compression TAG.
// According to EXIF 2.1 spec, a thumbnail can only be
// compressed using JPEG format. So the compress value should be
// "6". If the value is "1", it means we have an uncompressed
// thumbnail which can only be in TIFF format
if (pTag->us == 1)
{
DecodeTiffThumbnail(
lpStart,
lpData - 2,
bBigEndian,
count,
thumbImage
);
}
break;
default:
break;
}
}
lpData = (LPBYTE)(((IFD_TAG UNALIGNED*)lpData)+wEntries);
//
// get offset to next IFD
//
if ((INT) (lpData - lpStart) > (count + (INT) sizeof(DWORD)))
{
//
// Buffer too small
//
return E_FAIL;
}
offset = *(DWORD UNALIGNED *)lpData;
if (bBigEndian)
offset = SWAP_DWORD(offset);
if (offset)
{
lpData = (lpStart + offset);
}
} while ( offset );
return S_OK;
}
/**************************************************************************\
*
* Function Description:
*
* Gets the thumbnail from an APP1 marker
*
* Arguments:
*
* thumbImage - a pointer to the thumbnail image object to be created
* based on data extracted from the APP1 header
* APP1_marker - pointer to APP1 marker data
* APP1_length - length of APP1 segment
*
* Return Value:
*
* Status code
*
\**************************************************************************/
HRESULT GetAPP1Thumbnail(
OUT IImage **thumbImage,
IN PVOID APP1_marker,
IN UINT16 APP1_length
)
{
*thumbImage = NULL;
// Go past end of APP1 header
if (APP1_length < 6)
{
return S_OK;
}
PCHAR p = (PCHAR) APP1_marker;
if ((p[0] != 'E') ||
(p[1] != 'x') ||
(p[2] != 'i') ||
(p[3] != 'f'))
{
WARNING(("GetAPP1Thumbnail: APP1 header not EXIF"));
return S_OK;
}
APP1_length -= 6;
APP1_marker = (PVOID) (p + 6);
return DecodeApp1(thumbImage, (LPBYTE) APP1_marker, APP1_length);
}
/**************************************************************************\
*
* Function Description:
*
* Property value adjustment for EXIf IFD according to transform type. Like
* adjust X and Y dimention value if the image is rotated
*
* Arguments:
*
* [IN]lpBase------Pointer to EXIF data
* [IN]count-------Length of the data
* [IN]pTag--------Current EXIF tag
* [IN]bBigEndian--Flag for big endian
* [IN]uiXForm-----Transform method
*
* Return Value:
*
* Return S_OK if everything is OK. Otherwise, return error code
*
* Revision History:
*
* 2/01/2000 minliu
* Created it.
*
\**************************************************************************/
HRESULT
TransformExifIFD(
LPBYTE lpBase,
INT count,
IFD_TAG UNALIGNED * pTag,
BOOL bBigEndian,
UINT uiXForm,
UINT uiNewWidth,
UINT uiNewHeight
)
{
INT iPixXDimIndex = -1;
INT iPixYDimIndex = -1;
IFD_TAG tNewTag;
if ( (pTag->wType != TAG_TYPE_LONG) || (pTag->dwCount != 1) )
{
WARNING(("EXIF TransformExifIFD---Malformed exif pointer"));
return E_FAIL;
}
// Get pointer to EXIF IFD info
LPBYTE lpExif = lpBase + pTag->dwOffset;
// Figure out how many entries there are, and skip to the data section...
if ( (INT)((INT_PTR)lpExif + sizeof(WORD) - (INT_PTR)lpBase) > count )
{
WARNING(("EXIF TransformExifIFD---Buffer too small 1"));
return E_FAIL;
}
WORD wNumEntries = *(WORD*)lpExif;
lpExif += sizeof(WORD);
if ( bBigEndian == TRUE )
{
wNumEntries = SWAP_WORD(wNumEntries);
}
if ( (INT)((INT_PTR)lpExif + sizeof(IFD_TAG) *wNumEntries - (INT_PTR)lpBase)
> count )
{
WARNING(("EXIF TransformExifIFD---Buffer too small 2"));
return E_FAIL;
}
IFD_TAG UNALIGNED * pExifTag = (IFD_TAG UNALIGNED *)lpExif;
for (INT i = 0; i < wNumEntries; i++)
{
pExifTag = ((IFD_TAG*)lpExif) + i;
if ( bBigEndian == TRUE )
{
tNewTag = SwapIFD_TAG(pExifTag);
pExifTag = &tNewTag;
}
switch( pExifTag->wTag )
{
case EXIF_TAG_PIX_X_DIM:
if ( (pExifTag->dwCount != 1)
||( (pExifTag->wType != TAG_TYPE_SHORT)
&&(pExifTag->wType != TAG_TYPE_LONG) ) )
{
WARNING(("TransformExifIFD-PixelXDimension invalid format"));
}
else
{
iPixXDimIndex = i;
if ( uiNewWidth != 0 )
{
pExifTag->ul = uiNewWidth;
if ( bBigEndian == TRUE )
{
pExifTag->ul = SWAP_DWORD(pExifTag->ul);
}
}
}
break;
case EXIF_TAG_PIX_Y_DIM:
if ( (pExifTag->dwCount != 1)
||( (pExifTag->wType != TAG_TYPE_SHORT)
&&(pExifTag->wType != TAG_TYPE_LONG) ) )
{
WARNING(("TransformExifIFD PixelYDimension invalid format."));
}
else
{
iPixYDimIndex = i;
if ( uiNewHeight != 0 )
{
pExifTag->ul = uiNewHeight;
if ( bBigEndian == TRUE )
{
pExifTag->ul = SWAP_DWORD(pExifTag->ul);
}
}
}
break;
case TAG_THUMBNAIL_RESOLUTION_X:
case TAG_THUMBNAIL_RESOLUTION_Y:
if ( (pExifTag->wType == TAG_TYPE_LONG) && (pExifTag->dwCount == 1))
{
// Change the thumbnail tag to comments tag for now
// So no app will read this non-transformed thumbnail
pExifTag->wTag = EXIF_TAG_USER_COMMENT;
if ( bBigEndian == TRUE )
{
// Since we have to write the data back, we have to do
// another swap
tNewTag = SwapIFD_TAG(pExifTag);
// Find the dest address
pExifTag = ((IFD_TAG UNALIGNED *)lpExif) + i;
GpMemcpy(pExifTag, &tNewTag, sizeof(IFD_TAG));
}
}
break;
default:
break;
}// switch ( pExifTag->wTag )
}// Loop through all the TAGs
// Swap X dimension and Y dimension value if they exist and the
// transformation is 90 or 270 rotation
if ( (iPixXDimIndex >= 0) && (iPixYDimIndex >= 0)
&&((uiXForm == JXFORM_ROT_90) || (uiXForm == JXFORM_ROT_270)) )
{
// Get X resolution TAG
pTag = ((IFD_TAG UNALIGNED *)lpExif) + iPixXDimIndex;
// Set Y resolution tag as X resolution tag
pTag->wTag = EXIF_TAG_PIX_Y_DIM;
if ( bBigEndian == TRUE )
{
// Since we only change the wTag field, so we need only to swap
// this WORD, not the whole IFD_TAG
tNewTag.wTag = SWAP_WORD(pTag->wTag);
pTag->wTag = tNewTag.wTag;
}
// Get Y resolution TAG
pTag = ((IFD_TAG UNALIGNED*)lpExif) + iPixYDimIndex;
// Set X resolution tag as Y resolution tag
pTag->wTag = EXIF_TAG_PIX_X_DIM;
if ( bBigEndian == TRUE )
{
// Since we only change the wTag field, so we need only to swap
// this WORD, not the whole IFD_TAG
tNewTag.wTag = SWAP_WORD(pTag->wTag);
pTag->wTag = tNewTag.wTag;
}
}
return S_OK;
}// TransformExifIFD()
/**************************************************************************\
*
* Function Description:
*
* Property value adjustment according to transform type. Like adjust X
* and Y dimention value if the image is rotated
*
* Arguments:
* IN BYTE* pApp1Data-----Pointer to APP1 header
* IN UINT uiApp1Length--Total length of this APP1 header in bytes
* IN UINT uiXForm-------Transform method
*
* Return Value:
*
* Return S_OK if everything is OK. Otherwise, return error code
*
* Revision History:
*
* 2/01/2000 minliu
* Created it.
*
\**************************************************************************/
HRESULT
TransformApp1(
BYTE* pApp1Data,
UINT16 uiApp1Length,
UINT uiXForm,
UINT uiNewWidth,
UINT uiNewHeight
)
{
BYTE UNALIGNED* pcSrcData = pApp1Data;
BYTE UNALIGNED* lpData;
int iBytesRemaining;
BOOL bBigEndian = FALSE;
ULONG ulIfdOffset;
ULONG dwThumbnailOffset = 0;
ULONG dwThumbnailLength = 0;
int iXResolutionIndex = -1;
int iYResolutionIndex = -1;
BOOL bHasThumbNailIFD = FALSE; // Will be set to TRUE if we are in 1st
// IFD
// Decipher data
if ( uiApp1Length < 6 )
{
// Data length must be longer than 6 bytes
WARNING(("Exif TransformApp1---uiApp1Length too small"));
return E_FAIL;
}
// Check the header to see if it is EXIF
if ( (pcSrcData[0] != 'E')
||(pcSrcData[1] != 'x')
||(pcSrcData[2] != 'i')
||(pcSrcData[3] != 'f') )
{
WARNING(("Exif TransformApp1---Header is not Exif"));
return E_FAIL;
}
uiApp1Length -= 6;
pcSrcData += 6;
iBytesRemaining = uiApp1Length;
// Check if it is Big Endian or Little Endian
if ( *(UINT16 UNALIGNED*)(pcSrcData) == 0x4D4D )
{
bBigEndian = TRUE;
}
ulIfdOffset = *(UINT32 UNALIGNED*)(pcSrcData + 4);
if ( bBigEndian == TRUE )
{
ulIfdOffset = SWAP_DWORD(ulIfdOffset);
}
lpData = (pcSrcData + ulIfdOffset);
// Loop through all IFDs
do
{
// Get number of entries
if ((int)(lpData - pcSrcData) > (iBytesRemaining + (int)sizeof(UINT16)))
{
// Buffer too small
WARNING(("Exif TransformApp1---Buffer too small 1"));
return E_FAIL;
}
UINT16 wEntries = *(UINT16 UNALIGNED*)lpData;
if ( bBigEndian )
{
wEntries = SWAP_WORD(wEntries);
}
lpData += sizeof(UINT16);
// Loop through entries
if (((int)(lpData - pcSrcData) + ((int)wEntries * (int)sizeof(IFD_TAG)))
> (int)iBytesRemaining )
{
// Buffer too small
WARNING(("Exif TransformApp1---Buffer too small 2"));
return E_FAIL;
}
IFD_TAG UNALIGNED * pTag = (IFD_TAG UNALIGNED *)lpData;
IFD_TAG tNewTag;
IFD_TAG tTempTag;
for ( int i = 0; i < wEntries; ++i )
{
pTag = ((IFD_TAG UNALIGNED *)lpData) + i;
if ( bBigEndian == TRUE )
{
tNewTag = SwapIFD_TAG(pTag);
pTag = &tNewTag;
}
// Transform tag values
switch ( pTag->wTag )
{
case TAG_EXIF_IFD:
TransformExifIFD(pcSrcData, iBytesRemaining, pTag, bBigEndian,
uiXForm, uiNewWidth, uiNewHeight);
break;
case TAG_JPEG_INTER_FORMAT:
if ( (pTag->wType == TAG_TYPE_LONG) && (pTag->dwCount == 1) )
{
dwThumbnailOffset = pTag->dwOffset;
// Change the thumbnail tag to comments tag for now
// So no app will read this non-transformed thumbnail
pTag->wTag = EXIF_TAG_USER_COMMENT;
if ( bBigEndian == TRUE )
{
// Since we have to write the data back, we have to do
// another swap
tTempTag = SwapIFD_TAG(pTag);
// Find the dest address
pTag = ((IFD_TAG UNALIGNED *)lpData) + i;
GpMemcpy(pTag, &tTempTag, sizeof(IFD_TAG));
}
}
break;
case TAG_JPEG_INTER_LENGTH:
if ( (pTag->wType == TAG_TYPE_LONG) && (pTag->dwCount == 1) )
{
dwThumbnailLength = pTag->dwOffset;
// Change the thumbnail tag to comments tag for now
// So no app will read this non-transformed thumbnail
pTag->wTag = EXIF_TAG_USER_COMMENT;
if ( bBigEndian == TRUE )
{
// Since we have to write the data back, we have to do
// another swap
tTempTag = SwapIFD_TAG(pTag);
// Find the dest address
pTag = ((IFD_TAG UNALIGNED *)lpData) + i;
GpMemcpy(pTag, &tTempTag, sizeof(IFD_TAG));
}
}
break;
case TAG_X_RESOLUTION:
case TAG_Y_RESOLUTION:
case TAG_COMPRESSION:
case TAG_IMAGE_WIDTH:
case TAG_IMAGE_HEIGHT:
case TAG_RESOLUTION_UNIT:
if ( (bHasThumbNailIFD == TRUE)
&&(pTag->dwCount == 1) )
{
// Change the thumbnail tag to comments tag for now
// so that no app will read this non-transformed thumbnail
pTag->wTag = EXIF_TAG_USER_COMMENT;
if ( bBigEndian == TRUE )
{
// Since we have to write the data back, we have to do
// another swap
tTempTag = SwapIFD_TAG(pTag);
// Find the dest address
pTag = ((IFD_TAG UNALIGNED *)lpData) + i;
GpMemcpy(pTag, &tTempTag, sizeof(IFD_TAG));
}
}
break;
case EXIF_TAG_PIX_X_DIM:
if ( (pTag->dwCount == 1)
&&( (pTag->wType == TAG_TYPE_SHORT)
||(pTag->wType == TAG_TYPE_LONG) ) )
{
iXResolutionIndex = i;
if ( uiNewWidth != 0 )
{
pTag->ul = uiNewWidth;
if ( bBigEndian == TRUE )
{
pTag->ul = SWAP_DWORD(pTag->ul);
}
}
}
break;
case EXIF_TAG_PIX_Y_DIM:
if ( (pTag->dwCount == 1)
&&( (pTag->wType == TAG_TYPE_SHORT)
||(pTag->wType == TAG_TYPE_LONG) ) )
{
iYResolutionIndex = i;
if ( uiNewHeight != 0 )
{
pTag->ul = uiNewHeight;
if ( bBigEndian == TRUE )
{
pTag->ul = SWAP_DWORD(pTag->ul);
}
}
}
break;
default:
// We don't care the rest tags
break;
}// switch
#if 0
// !!!Don't remove this code. This is the place where we need to add
// code in V2 to transform the thumbnail
//
// Launch another transformation process here for thumbnail
if ( (dwThumbnailOffset != 0) && (dwThumbnailLength != 0) )
{
// We got the bits
void* pBits = pcSrcData + dwThumbnailOffset;
FILE* hFile = fopen("aaa.jpg", "w");
fwrite(pBits, 1, (size_t)dwThumbnailLength, hFile);
fclose(hFile);
}
#endif
}// Loop all the entries
// Swap X resolution and Y resolution value if they exist and the
// transformation is 90 or 270 rotation
if ( (iXResolutionIndex >= 0) && (iYResolutionIndex >= 0)
&&((uiXForm == JXFORM_ROT_90) || (uiXForm == JXFORM_ROT_270)) )
{
// Get X resolution TAG
pTag = ((IFD_TAG UNALIGNED *)lpData) + iXResolutionIndex;
// Set Y resolution tag as X resolution tag
pTag->wTag = EXIF_TAG_PIX_Y_DIM;
if ( bBigEndian == TRUE )
{
// Since we only change the wTag field, so we need only to swap
// this WORD, not the whole IFD_TAG
tNewTag.wTag = SWAP_WORD(pTag->wTag);
pTag->wTag = tNewTag.wTag;
}
// Get Y resolution TAG
pTag = ((IFD_TAG UNALIGNED *)lpData) + iYResolutionIndex;
// Set X resolution tag as Y resolution tag
pTag->wTag = EXIF_TAG_PIX_X_DIM;
if ( bBigEndian == TRUE )
{
// Since we only change the wTag field, so we need only to swap
// this WORD, not the whole IFD_TAG
tNewTag.wTag = SWAP_WORD(pTag->wTag);
pTag->wTag = tNewTag.wTag;
}
}
lpData = (BYTE*)(((IFD_TAG UNALIGNED *)lpData) + wEntries);
// get offset to next IFD
if ((int) (lpData - pcSrcData) > (iBytesRemaining +(int)sizeof(UINT32)))
{
// Buffer too small
WARNING(("Exif TransformApp1---Buffer too small 3"));
return E_FAIL;
}
ulIfdOffset = *(UINT32 UNALIGNED*)lpData;
if ( bBigEndian == TRUE )
{
ulIfdOffset = SWAP_DWORD(ulIfdOffset);
}
if ( ulIfdOffset )
{
bHasThumbNailIFD = TRUE;
lpData = (pcSrcData + ulIfdOffset);
}
} while ( ulIfdOffset );
return S_OK;
}// TransformApp1()
/**************************************************************************\
*
* Function Description:
*
* Add a property item into the InternalPropertyItem list
*
* Arguments:
*
* [IN/OUT]pTail---A pointer to the tail of the last entry in the property list
* [IN]lpBase------Base address for APP1 data
* [IN]pTag--------Current IFD tag
* [IN]bBigEndian--Flag for big endian
* [IN/OUT]puiListSize--Current list size
*
* Note: For BigEndian case, the caller has swapped everything in the TAG, but
* not in the offset section. So if we want to get values from pTag->us,
* or pTag->l etc., we don't need to swap them any more
*
* Return Value:
*
* Status code
*
* Revision History:
*
* 02/27/2000 MinLiu
* Created it.
*
\**************************************************************************/
HRESULT
AddPropertyListDirect(
InternalPropertyItem* pTail,
LPBYTE lpBase,
IFD_TAG UNALIGNED * pTag,
BOOL bBigEndian,
UINT* puiListSize
)
{
// Prepare a new property item to be attached to the property link list
InternalPropertyItem* pNewItem = new InternalPropertyItem();
if ( pNewItem == NULL )
{
WARNING(("AddPropertyListDirect---Out of memory"));
return E_OUTOFMEMORY;
}
pNewItem->id = pTag->wTag;
pNewItem->type = pTag->wType;
HRESULT hResult = S_OK;
VOID UNALIGNED* pValueBuffer = NULL;
BOOL fHasAllocatedBuffer = FALSE;
UINT uiLength = 0;
VOID* pValue = NULL;
if ( pTag->dwCount > 0 )
{
// Doesn't make sense if dwCount < 1. But we still need to add it to the
// list so that we won't lose any property information
switch ( pTag->wType )
{
case TAG_TYPE_BYTE:
{
LPSTR psz = NULL;
if ( pTag->dwCount <= 4 )
{
psz = (LPSTR)&pTag->dwOffset;
}
else
{
psz = (LPSTR)(lpBase + pTag->dwOffset);
}
if ( bBigEndian )
{
char cTemp0 = psz[0];
char cTemp1 = psz[1];
psz[0] = psz[3];
psz[1] = psz[2];
psz[2] = cTemp1;
psz[3] = cTemp0;
}
uiLength = pTag->dwCount;
pValue = (PVOID)psz;
break;
}
case TAG_TYPE_ASCII:
{
LPSTR psz = NULL;
if ( pTag->dwCount <= 4 )
{
psz = (LPSTR)&pTag->dwOffset;
}
else
{
psz = (LPSTR)(lpBase + pTag->dwOffset);
}
// According to the EXIF2.1 spec, an ASCII type means "an 8-bit byte
// containing one 7-bit ASCII code. The final byte is terminated
// with NULL".
// But in real life, there are cameras, like "Canon PowerShot S100"
// which doesn't follow this rule in some of the ASCII tags it
// produces, see Windows bug#403951. So we have to
// protect ourselves running into buffer over-run problem.
if (psz[pTag->dwCount - 1] == '\0')
{
uiLength = strlen(psz) + 1;
pValue = (PVOID)psz;
}
else
{
// Apparently the source doesn't have a NULL terminator at the
// place where it should be. Do safe copy.
// Note: some cameras do weird things, like JVC GR_DVL915U, it
// claims the camera model field has 20 bytes, like this:
// "GR-DV***[00][00][00][00][00][00][00][00][00][00][00][00]"
// We decided to take only the first 9 bytes in this case to
// report the camera model. Because it doesn't make sense for
// our SHELL UI to display extra 12 bytes of NULL chars there.
// That's also the reason we need to do a strlen() here.
UINT uiTempLength = pTag->dwCount + 1; // Including the NULL
pValueBuffer = (PVOID)GpMalloc(uiTempLength);
if (pValueBuffer == NULL)
{
WARNING(("AddPtyLstDir-Fail alloc %x bytes",uiTempLength));
hResult = E_OUTOFMEMORY;
goto CleanUp;
}
// Set this flag so that the temp buffer will be freed at the
// end of this function
fHasAllocatedBuffer = TRUE;
// Only copy the first "pTag->dwCount" bytes
GpMemcpy(pValueBuffer, (BYTE*)(psz), pTag->dwCount);
// Stick a NULL at the end
((char*)pValueBuffer)[pTag->dwCount] = '\0';
// Re-calculate the length
uiLength = strlen(((char*)pValueBuffer)) + 1;
pValue = (PVOID)pValueBuffer;
}
break;
}
case TAG_TYPE_SHORT:
uiLength = pTag->dwCount * sizeof(short);
pValueBuffer = (VOID*)GpMalloc(uiLength);
if ( pValueBuffer == NULL )
{
WARNING(("AddPropertyLstDir-Fail to alloc %x bytes", uiLength));
hResult = E_OUTOFMEMORY;
goto CleanUp;
}
fHasAllocatedBuffer = TRUE;
switch ( pTag->dwCount )
{
case 1:
// One short value.
GpMemcpy(pValueBuffer, &pTag->us, uiLength);
break;
case 2:
// Two short values
// Note: In this case, pTag->dwOffset stores TWO short values,
// not the offset.
// In big endian case, since dwOffset has already been swapped.
// So it has the little endian order now. But the order for two
// SHORTs is still not right. It stores the 1st SHORT value in
// its higher 2 bytes and 2nd SHORT value in its lower two
// bytes.
// Here is an example: Say original value is 0x12345678 in big
// endian mode. It was intend to be two SHORTs of 0x1234 and
// 0x5678. So the correct little endian value for it should be
// two SHORTs of 0x7856 and 0x3412. When the caller swapped the
// whole TAG, it swaps the LONG value of 0x12345678 to
// 0x78563412. So in order to get two SHORTs of little endian to
// be stored in a LONG position, we should do the following code
if ( bBigEndian )
{
INT16* pTemp = (INT16*)pValueBuffer;
*pTemp++ = (INT16)((pTag->dwOffset & 0xffff0000) >> 16);
*pTemp = (INT16)(pTag->dwOffset & 0x0000ffff);
}
else
{
GpMemcpy(pValueBuffer, &pTag->dwOffset, uiLength);
}
break;
default:
// More than 2 SHORT values, that is, more than 4 bytes of value
// So we have to get it from the offset section
GpMemcpy(pValueBuffer, (BYTE*)(lpBase + pTag->dwOffset),
uiLength);
if ( bBigEndian )
{
// Swap all the SHORT values
INT16* pTemp = (INT16*)pValueBuffer;
for ( int i = 0; i < (int)pTag->dwCount; ++i )
{
*pTemp++ = SWAP_WORD(*pTemp);
}
break;
}
}// switch (dwCount)
pValue = pValueBuffer;
break;
case TAG_TYPE_LONG:
case TAG_TYPE_SLONG:
uiLength = pTag->dwCount * sizeof(long);
if ( pTag->dwCount == 1 )
{
// If there is only one LONG value, we can get it from pTag->l
// directly, no swap is needed even for Big Endian case
pValueBuffer = &pTag->l;
}
else
{
if ( bBigEndian )
{
// This is a big-endian image. So we create a temp buffer
// here. Get the original values to this buffer, then swap
// it
pValueBuffer = (VOID*)GpMalloc(uiLength);
if ( pValueBuffer == NULL )
{
WARNING(("AddPropertyLstDir--Alloc %x bytes",uiLength));
hResult = E_OUTOFMEMORY;
goto CleanUp;
}
fHasAllocatedBuffer = TRUE;
// We have more than 4 bytes of value. So it has to be
// stored in the offset section
GpMemcpy(pValueBuffer, (BYTE*)(lpBase + pTag->dwOffset),
uiLength);
// Swap all the LONG values
INT32* pTemp = (INT32*)pValueBuffer;
for ( int i = 0; i < (int)pTag->dwCount; ++i )
{
*pTemp++ = SWAP_DWORD(*pTemp);
}
}// Big endian case
else
{
// For none BigEndian case, we can get the value directy
// from the source
pValueBuffer = (VOID*)(lpBase + pTag->dwOffset);
}
}// (dwCount > 1)
pValue = pValueBuffer;
break;
case TAG_TYPE_RATIONAL:
case TAG_TYPE_SRATIONAL:
// The size for this item is dwCount times 2 longs ( = RATIONAL)
uiLength = pTag->dwCount * 2 * sizeof(long);
if ( bBigEndian )
{
// This is a big-endian image. So we create a temp buffer here.
// Get the original values to this buffer, then swap it if
// necessary
pValueBuffer = (PVOID)GpMalloc(uiLength);
if ( pValueBuffer == NULL )
{
WARNING(("AddPropertyLstDir-Fail alloc %x bytes",uiLength));
hResult = E_OUTOFMEMORY;
goto CleanUp;
}
fHasAllocatedBuffer = TRUE;
GpMemcpy(pValueBuffer, (BYTE*)(lpBase + pTag->dwOffset),
uiLength);
// Casting the source value to INT32 world and swap it
INT32* piTemp = (INT32*)pValueBuffer;
for ( int i = 0; i < (int)pTag->dwCount; ++i )
{
// A Rational is composed of two long values. The first one
// is the numerator and the second one is the denominator.
INT32 lNum = *piTemp;
INT32 lDen = *(piTemp + 1);
// Swap the value
lNum = SWAP_DWORD(lNum);
lDen = SWAP_DWORD(lDen);
// Put it back
*piTemp = lNum;
*(piTemp + 1) = lDen;
piTemp += 2;
}
}// Big endian case
else
{
// For none BigEndian case, we can get the value directy from
// the source
pValueBuffer = (VOID*)(lpBase + pTag->dwOffset);
}
pValue = pValueBuffer;
break;
default:
WARNING(("EXIF: Unknown tag type"));
// Note: the caller should not call this function if the type is
// TAG_TYPE_UNDEFINED
hResult = E_FAIL;
goto CleanUp;
}// switch on tag type
}// ( pTag->dwCount > 0 )
if ( uiLength != 0 )
{
pNewItem->value = GpMalloc(uiLength);
if ( pNewItem->value == NULL )
{
WARNING(("AddPropertyListDirect fail to alloca %d bytes",uiLength));
hResult = E_OUTOFMEMORY;
goto CleanUp;
}
GpMemcpy(pNewItem->value, pValue, uiLength);
}
else
{
pNewItem->value = NULL;
}
pNewItem->length = uiLength;
pTail->pPrev->pNext = pNewItem;
pNewItem->pPrev = pTail->pPrev;
pNewItem->pNext = pTail;
pTail->pPrev = pNewItem;
*puiListSize += uiLength;
// Set hResult to S_OK so pNewItem won't be deleted below
hResult = S_OK;
CleanUp:
// If we fail into here and hResult is not S_OK, it means something is wrong
// Do some clean ups before return
if ( (hResult != S_OK) && (pNewItem != NULL) )
{
delete pNewItem;
}
if ( fHasAllocatedBuffer == TRUE )
{
// If we have allocated the buffer, then free it
GpFree(pValueBuffer);
}
return hResult;
}// AddPropertyListDirect()
/**************************************************************************\
*
* Function Description:
*
* Decodes an EXIF IFD into a property storage
*
* Arguments:
*
* propStgImg -- The property storage to modify
* lpBase -- A pointer to the beginning of the APP1 header
* count -- The length of the APP1 header
* pTag -- A pointer to the current IFD tag
*
* Return Value:
*
* Status code
*
* Revision History:
*
* 02/27/2000 MinLiu
* Created it.
*
\**************************************************************************/
HRESULT
BuildPropertyListFromExifIFD(
InternalPropertyItem* pTail,
UINT* puiListSize,
UINT* puiNumOfItems,
LPBYTE lpBase,
INT count,
IFD_TAG UNALIGNED * pTag,
BOOL bBigEndian
)
{
HRESULT hResult = S_OK;
UINT uiListSize = *puiListSize;
UINT uiNumOfItems = *puiNumOfItems;
if ( (pTag->wType != TAG_TYPE_LONG) || (pTag->dwCount != 1) )
{
WARNING(("BuildPropertyListFromExifIFD: Malformed exif ptr"));
return E_FAIL;
}
// Get pointer to EXIF IFD info
LPBYTE lpExif = lpBase + pTag->dwOffset;
// Figure out how many entries there are, and skip to the data section...
if ( (INT)((INT_PTR)lpExif + sizeof(WORD) - (INT_PTR)lpBase) > count )
{
WARNING(("BuildPropertyListFromExifIFD---Buffer too small"));
return E_FAIL;
}
WORD wNumEntries = *(WORD UNALIGNED *)lpExif;
lpExif += sizeof(WORD);
if ( bBigEndian )
{
wNumEntries = SWAP_WORD(wNumEntries);
}
if ( (INT)((INT_PTR)lpExif + sizeof(IFD_TAG) * wNumEntries
-(INT_PTR)lpBase) > count )
{
WARNING(("BuildPropertyListFromExifIFD---Buffer too small"));
return E_FAIL;
}
IFD_TAG UNALIGNED * pExifTag = (IFD_TAG UNALIGNED *)lpExif;
UINT valueLength;
for ( INT i = 0; i < wNumEntries; ++i )
{
IFD_TAG tNewTag;
pExifTag = ((IFD_TAG UNALIGNED *)lpExif) + i;
if ( bBigEndian == TRUE )
{
tNewTag = SwapIFD_TAG(pExifTag);
pExifTag = &tNewTag;
}
// No need to parse these tags. But we can't add any unknown type
// into the list because we don't know its length
if (pExifTag->wTag == EXIF_TAG_INTEROP)
{
hResult = BuildInterOpPropertyList(
pTail,
&uiListSize,
&uiNumOfItems,
lpBase,
count,
pExifTag,
bBigEndian
);
}
else if ( pExifTag->wType != TAG_TYPE_UNDEFINED )
{
uiNumOfItems++;
hResult = AddPropertyListDirect(pTail, lpBase, pExifTag,
bBigEndian, &uiListSize);
}
else if ( pExifTag->dwCount <= 4 )
{
// According to the spec, an "UNDEFINED" value is an 8-bits type
// that can take any value depends on the field.
// In case where the value fits in 4 bytes, the value itself is
// recorded. That is, "dwOffset" is the value for these "dwCount"
// fields.
uiNumOfItems++;
uiListSize += pExifTag->dwCount;
LPSTR pVal = (LPSTR)&pExifTag->dwOffset;
if ( bBigEndian )
{
char cTemp0 = pVal[0];
char cTemp1 = pVal[1];
pVal[0] = pVal[3];
pVal[1] = pVal[2];
pVal[2] = cTemp1;
pVal[3] = cTemp0;
}
hResult = AddPropertyList(pTail,
pExifTag->wTag,
pExifTag->dwCount,
pExifTag->wType,
pVal);
}// ( pExifTag->dwCount <= 4 )
else
{
uiNumOfItems++;
uiListSize += pExifTag->dwCount;
PVOID pTemp = lpBase + pExifTag->dwOffset;
hResult = AddPropertyList(pTail,
pExifTag->wTag,
pExifTag->dwCount,
TAG_TYPE_UNDEFINED,
pTemp);
}// ( pExifTag->dwCount > 4 )
if ( FAILED(hResult) )
{
WARNING(("BuildPropertyListFromExifIFD---AddPropertyList failed"));
break;
}
}// Loop through all the EXIF IFD entries
*puiListSize = uiListSize;
*puiNumOfItems = uiNumOfItems;
return hResult;
}// BuildPropertyListFromExifIFD()
/**************************************************************************\
*
* Function Description:
*
* Decodes the EXIF app1 header and build a PropertyItem list
*
* Arguments:
*
* [OUT] ppList-------- A pointer to a list of property items
* [OUT] puiListSize--- The total size of the property list, in bytes.
* [OUT] puiNumOfItems- Total number of property items
* [IN] pStart ------- A pointer to the beginning of the APP1 header
* [IN] iApp1Length -- The length of the APP1 header
*
* Return Value:
*
* Status code
*
* Revision History:
*
* 02/27/2000 MinLiu
* Created it.
*
\**************************************************************************/
HRESULT
BuildApp1PropertyList(
InternalPropertyItem* pTail,
UINT* puiListSize,
UINT* puiNumOfItems,
LPBYTE lpAPP1Data,
UINT16 iApp1Length
)
{
DWORD dwThumbnailOffset = 0;
DWORD dwThumbnailLength = 0;
LPBYTE lpData = NULL;
ULONG ulIfdOffset = 0;
WORD wEntries = 0;
HRESULT hResult = S_OK;
// Input parameter validation
if ( (pTail == NULL) || (puiListSize == NULL)
||(puiNumOfItems == NULL) || (lpAPP1Data == NULL) )
{
return E_FAIL;
}
if ( iApp1Length < 6 )
{
// This APP1 header apparently doesn't have information.
// Note: we should return S_OK, not fail. Because this function call
// succeed.
*puiListSize = 0;
*puiNumOfItems = 0;
return S_OK;
}
// Check the signature
PCHAR p = (PCHAR)lpAPP1Data;
if ( (p[0] != 'E')
||(p[1] != 'x')
||(p[2] != 'i')
||(p[3] != 'f') )
{
WARNING(("BuildApp1PropertyList: APP1 header not EXIF"));
return S_OK;
}
// At the beginning of APP1 chunk is "Exif00", the signature part. All the
// "offset" is relative to the bytes after this 6 bytes. So we move forward
// 6 bytes now
iApp1Length -= 6;
lpAPP1Data = (LPBYTE)(p + 6);
// The next 2 bytes is either 0x4D4D or 0x4949 to indicate endian type
BOOL bBigEndian = (*(WORD UNALIGNED *)(lpAPP1Data) == 0x4D4D);
// The next two bytes is fixed: 0x2A00, signature
// After this two bytes, it is the IFD offset, 4 bytes
ulIfdOffset = *(DWORD UNALIGNED *)(lpAPP1Data + 4);
if ( bBigEndian )
{
ulIfdOffset = SWAP_DWORD(ulIfdOffset);
}
lpData = (lpAPP1Data + ulIfdOffset);
UINT uiNumOfItems = 0;
UINT uiListSize = 0;
UINT valueLength;
BOOL bHasThumbNailIFD = FALSE;
// Loop through IFD's
do
{
// Get number of entries
if ( (INT)(lpData - lpAPP1Data) > (iApp1Length + (INT)sizeof(WORD)) )
{
// Buffer too small
WARNING(("BuildApp1PropertyList--Input buffer size is not right"));
return E_FAIL;
}
wEntries = *(WORD UNALIGNED *)lpData;
if ( bBigEndian )
{
wEntries = SWAP_WORD(wEntries);
}
lpData += sizeof(WORD);
// Loop through entries
if ( ((INT)(lpData - lpAPP1Data)
+ ((INT)wEntries * (INT)sizeof(IFD_TAG))) > (INT)iApp1Length )
{
// Buffer too small
WARNING(("BuildApp1PropertyList--Input buffer size is not right"));
return E_FAIL;
}
IFD_TAG UNALIGNED * pTag = (IFD_TAG UNALIGNED *)lpData;
ULONG ulThumbnailOffset = 0;
ULONG ulThumbnailLength = 0;
for ( INT i = 0; i < wEntries; ++i )
{
pTag = ((IFD_TAG UNALIGNED *)lpData) + i;
IFD_TAG tNewTag;
if ( bBigEndian == TRUE )
{
tNewTag = SwapIFD_TAG(pTag);
pTag = &tNewTag;
}
// Extract properties
switch (pTag->wTag)
{
case TAG_EXIF_IFD:
case TAG_GPS_IFD:
hResult = BuildPropertyListFromExifIFD(
pTail,
&uiListSize,
&uiNumOfItems,
lpAPP1Data,
iApp1Length,
pTag,
bBigEndian
);
break;
// Note: For JPEG thumbnail information, these following 2 TAGs
// will each come once. We can store the THUMBNAIL_DATA info only
// after we see both of the TAGs
case TAG_JPEG_INTER_FORMAT:
if ( (pTag->wType != TAG_TYPE_LONG) || (pTag->dwCount != 1) )
{
WARNING(("InterchangeFormat unit found, invalid format."));
}
else
{
ulThumbnailOffset = pTag->dwOffset;
valueLength = sizeof(UINT32);
uiNumOfItems++;
uiListSize += valueLength;
hResult = AddPropertyList(pTail, pTag->wTag,
valueLength, TAG_TYPE_LONG,
&pTag->dwOffset);
}
if ( (ulThumbnailLength != 0) && (ulThumbnailOffset != 0) )
{
if ( (ulThumbnailOffset + ulThumbnailLength) > iApp1Length )
{
WARNING(("BuildApp1PropertyList-Thumb offset too big"));
}
else
{
// We are sure we have a thumbnail image, add it to the
// property list
valueLength = ulThumbnailLength;
uiNumOfItems++;
uiListSize += valueLength;
hResult = AddPropertyList(
pTail,
TAG_THUMBNAIL_DATA,
valueLength,
TAG_TYPE_BYTE,
lpAPP1Data + ulThumbnailOffset);
}
}
break;
case TAG_JPEG_INTER_LENGTH:
if ( (pTag->wType != TAG_TYPE_LONG) || (pTag->dwCount != 1) )
{
WARNING(("InterchangeLength unit found, invalid format."));
}
else
{
ulThumbnailLength = pTag->ul;
valueLength = sizeof(UINT32);
uiNumOfItems++;
uiListSize += valueLength;
hResult = AddPropertyList(pTail, pTag->wTag,
valueLength, TAG_TYPE_LONG,
&pTag->ul);
}
if ( (ulThumbnailLength != 0) && (ulThumbnailOffset != 0) )
{
// Check if we really has so much data in the buffer
// Note: we need this check here because some camera vandors
// put wrong thumbnail info in the header. If we don't check
// the offset or size here, we will be in trouble
// With the check below, if we find this kind of image, we
// just ignore the thumbnail data section
if ( (ulThumbnailOffset + ulThumbnailLength) > iApp1Length )
{
WARNING(("BuildApp1PropertyList-Thumb offset too big"));
}
else
{
// We are sure we have a thumbnail image, add it to the
// property list
valueLength = ulThumbnailLength;
uiNumOfItems++;
uiListSize += valueLength;
hResult = AddPropertyList(pTail, TAG_THUMBNAIL_DATA,
valueLength, TAG_TYPE_BYTE,
lpAPP1Data + ulThumbnailOffset);
}
}
break;
default:
// Do a TAG id checking if the tags are in 1st IFD.
// Note: the reason we need to do this is that EXIF spec doesn't
// specify a way to distinguish the IFD locations for some of
// TAGs, like "Compression Scheme", "Resolution Unit" etc. This
// causes problem for user reading the TAG id and understand it.
// It also get confused when doing a saving. For now, we
// distinguish them by assign them a different ID. When save the
// image, we convert it back so that the image we write out
// still complies with EXIF spec.
if ( bHasThumbNailIFD == TRUE )
{
switch ( pTag->wTag )
{
case TAG_IMAGE_WIDTH:
pTag->wTag = TAG_THUMBNAIL_IMAGE_WIDTH;
break;
case TAG_IMAGE_HEIGHT:
pTag->wTag = TAG_THUMBNAIL_IMAGE_HEIGHT;
break;
case TAG_BITS_PER_SAMPLE:
pTag->wTag = TAG_THUMBNAIL_BITS_PER_SAMPLE;
break;
case TAG_COMPRESSION:
pTag->wTag = TAG_THUMBNAIL_COMPRESSION;
// Hit thumbnail compression TAG.
// According to EXIF 2.1 spec, a thumbnail can only be
// compressed using JPEG format. So the compress value should be
// "6". If the value is "1", it means we have an uncompressed
// thumbnail which can only be in TIFF format
if (pTag->us == 1)
{
hResult = ConvertTiffThumbnailToJPEG(
lpAPP1Data,
lpData - 2,
bBigEndian,
iApp1Length,
pTail,
&uiNumOfItems,
&uiListSize
);
}
break;
case TAG_PHOTOMETRIC_INTERP:
pTag->wTag = TAG_THUMBNAIL_PHOTOMETRIC_INTERP;
break;
case TAG_IMAGE_DESCRIPTION:
pTag->wTag = TAG_THUMBNAIL_IMAGE_DESCRIPTION;
break;
case TAG_EQUIP_MAKE:
pTag->wTag = TAG_THUMBNAIL_EQUIP_MAKE;
break;
case TAG_EQUIP_MODEL:
pTag->wTag = TAG_THUMBNAIL_EQUIP_MODEL;
break;
case TAG_STRIP_OFFSETS:
pTag->wTag = TAG_THUMBNAIL_STRIP_OFFSETS;
break;
case TAG_ORIENTATION:
pTag->wTag = TAG_THUMBNAIL_ORIENTATION;
break;
case TAG_SAMPLES_PER_PIXEL:
pTag->wTag = TAG_THUMBNAIL_SAMPLES_PER_PIXEL;
break;
case TAG_ROWS_PER_STRIP:
pTag->wTag = TAG_THUMBNAIL_ROWS_PER_STRIP;
break;
case TAG_STRIP_BYTES_COUNT:
pTag->wTag = TAG_THUMBNAIL_STRIP_BYTES_COUNT;
break;
case TAG_X_RESOLUTION:
pTag->wTag = TAG_THUMBNAIL_RESOLUTION_X;
break;
case TAG_Y_RESOLUTION:
pTag->wTag = TAG_THUMBNAIL_RESOLUTION_Y;
break;
case TAG_PLANAR_CONFIG:
pTag->wTag = TAG_THUMBNAIL_PLANAR_CONFIG;
break;
case TAG_RESOLUTION_UNIT:
pTag->wTag = TAG_THUMBNAIL_RESOLUTION_UNIT;
break;
case TAG_TRANSFER_FUNCTION:
pTag->wTag = TAG_THUMBNAIL_TRANSFER_FUNCTION;
break;
case TAG_SOFTWARE_USED:
pTag->wTag = TAG_THUMBNAIL_SOFTWARE_USED;
break;
case TAG_DATE_TIME:
pTag->wTag = TAG_THUMBNAIL_DATE_TIME;
break;
case TAG_ARTIST:
pTag->wTag = TAG_THUMBNAIL_ARTIST;
break;
case TAG_WHITE_POINT:
pTag->wTag = TAG_THUMBNAIL_WHITE_POINT;
break;
case TAG_PRIMAY_CHROMATICS:
pTag->wTag = TAG_THUMBNAIL_PRIMAY_CHROMATICS;
break;
case TAG_YCbCr_COEFFICIENTS:
pTag->wTag = TAG_THUMBNAIL_YCbCr_COEFFICIENTS;
break;
case TAG_YCbCr_SUBSAMPLING:
pTag->wTag = TAG_THUMBNAIL_YCbCr_SUBSAMPLING;
break;
case TAG_YCbCr_POSITIONING:
pTag->wTag = TAG_THUMBNAIL_YCbCr_POSITIONING;
break;
case TAG_REF_BLACK_WHITE:
pTag->wTag = TAG_THUMBNAIL_REF_BLACK_WHITE;
break;
case TAG_COPYRIGHT:
pTag->wTag = TAG_THUMBNAIL_COPYRIGHT;
break;
default:
break;
}
}
if ( pTag->wType != TAG_TYPE_UNDEFINED )
{
hResult = AddPropertyListDirect(pTail, lpAPP1Data, pTag,
bBigEndian, &uiListSize);
}
else
{
if ( pTag->dwCount > 4 )
{
hResult = AddPropertyList(pTail,
pTag->wTag,
pTag->dwCount,
TAG_TYPE_UNDEFINED,
lpAPP1Data + pTag->dwOffset);
}
else
{
hResult = AddPropertyList(pTail,
pTag->wTag,
pTag->dwCount,
TAG_TYPE_UNDEFINED,
&pTag->dwOffset);
}
if (SUCCEEDED(hResult))
{
uiListSize += pTag->dwCount;
}
}
if (SUCCEEDED(hResult))
{
uiNumOfItems++;
}
break;
}
if (FAILED(hResult))
{
break;
}
}// Loop through all the entries in current IFD
if (FAILED(hResult))
{
break;
}
lpData = (LPBYTE)(((IFD_TAG*)lpData) + wEntries);
// Get offset to next IFD
if ((INT)(lpData - lpAPP1Data) > (iApp1Length + (INT)sizeof(DWORD)))
{
// Buffer too small
WARNING(("BuildApp1PropertyList--Input buffer size is not right"));
return E_FAIL;
}
ulIfdOffset = *(DWORD UNALIGNED *)lpData;
if ( bBigEndian )
{
ulIfdOffset = SWAP_DWORD(ulIfdOffset);
}
if ( ulIfdOffset )
{
// We find 1st IFD in this image, thumbnail IFD
bHasThumbNailIFD = TRUE;
lpData = (lpAPP1Data + ulIfdOffset);
}
} while ( ulIfdOffset );
*puiNumOfItems += uiNumOfItems;
*puiListSize += uiListSize;
return hResult;
}// BuildApp1PropertyList()
/**************************************************************************\
*
* Function Description:
*
* Extract EXIF information, like resolution etc, from the header and set
* the j_decompress_ptr accordingly
*
* Arguments:
*
* [IN/OUT] cinfo-----JPEG decompress structure
* [IN] pApp1Data-----Pointer to APP1 header
* [IN] uiApp1Length--Total length of this APP1 header in bytes
*
* Return Value:
*
* Status code
*
* Revision History:
*
* 02/29/2000 MinLiu
* Created it.
*
\**************************************************************************/
HRESULT
ReadApp1HeaderInfo(
j_decompress_ptr cinfo,
BYTE* pApp1Data,
UINT16 uiApp1Length
)
{
BYTE* pcSrcData = pApp1Data;
BYTE* lpData;
int iBytesRemaining;
BOOL bBigEndian = FALSE;
ULONG ulIfdOffset;
// Decipher data
if ( uiApp1Length < 6 )
{
// Data length must be longer than 6 bytes
return E_FAIL;
}
// Check the header to see if it is EXIF
if ( (pcSrcData[0] != 'E')
||(pcSrcData[1] != 'x')
||(pcSrcData[2] != 'i')
||(pcSrcData[3] != 'f') )
{
// It is not EXIF APP1 header. We don't bother to check the header
// Note: we don't want the the APP to fail. Just return S_OK here,
// not E_FAIL
return S_OK;
}
uiApp1Length -= 6;
pcSrcData += 6;
iBytesRemaining = uiApp1Length;
// Check if it is Big Endian or Little Endian
if ( *(UINT16 UNALIGNED *)(pcSrcData) == 0x4D4D )
{
bBigEndian = TRUE;
}
ulIfdOffset = *(UINT32 UNALIGNED *)(pcSrcData + 4);
if ( bBigEndian )
{
ulIfdOffset = SWAP_DWORD(ulIfdOffset);
}
// Get the pointer to the 1st IFD data structure, the primary image
// structure
lpData = (pcSrcData + ulIfdOffset);
// Get number of entries
if ( (int)(lpData - pcSrcData) > (iBytesRemaining + (int)sizeof(UINT16)) )
{
// Buffer too small
return E_FAIL;
}
UINT16 wEntries = *(UINT16 UNALIGNED *)lpData;
if ( bBigEndian )
{
wEntries = SWAP_WORD(wEntries);
}
lpData += sizeof(UINT16);
// Loop through entries
if ( ((int)(lpData - pcSrcData) + ((int)wEntries * (int)sizeof(IFD_TAG)))
> (int)iBytesRemaining )
{
// Buffer too small
return E_FAIL;
}
IFD_TAG UNALIGNED * pTag = (IFD_TAG UNALIGNED *)lpData;
IFD_TAG tNewTag;
for ( int i = 0; i < wEntries; ++i )
{
pTag = ((IFD_TAG UNALIGNED *)lpData) + i;
if ( bBigEndian == TRUE )
{
tNewTag = SwapIFD_TAG(pTag);
pTag = &tNewTag;
}
// Extract resolution information from IFD[0]
switch ( pTag->wTag )
{
case TAG_X_RESOLUTION:
if ( (pTag->wType != TAG_TYPE_RATIONAL)
||(pTag->dwCount != 1) )
{
WARNING(("TAG: XResolution found, invalid format."));
}
else
{
LONG UNALIGNED * pLong = (LONG*)(pcSrcData + pTag->dwOffset);
LONG num,den;
DOUBLE dbl;
num = *pLong++;
den = *pLong;
if ( bBigEndian )
{
num = SWAP_DWORD(num);
den = SWAP_DWORD(den);
}
dbl = (DOUBLE)num / (DOUBLE)den;
cinfo->X_density = (UINT16)dbl;
}
break;
case TAG_Y_RESOLUTION:
if ( (pTag->wType != TAG_TYPE_RATIONAL)
||(pTag->dwCount != 1) )
{
WARNING(("TAG: YResolution found, invalid format."));
}
else
{
LONG UNALIGNED * pLong = (LONG*)(pcSrcData + pTag->dwOffset);
LONG num,den;
DOUBLE dbl;
num = *pLong++;
den = *pLong;
if ( bBigEndian )
{
num = SWAP_DWORD(num);
den = SWAP_DWORD(den);
}
dbl = (DOUBLE)num / (DOUBLE)den;
cinfo->Y_density = (UINT16)dbl;
}
break;
case TAG_RESOLUTION_UNIT:
if ( (pTag->wType != TAG_TYPE_SHORT) || (pTag->dwCount != 1) )
{
WARNING(("TAG: Resolution unit found, invalid format."))
}
else
{
switch ( pTag->us )
{
case 2:
// Resolution unit: inch
// Note: the convension for resolution unit in EXIF and in
// IJG JPEG library is different. In EXIF, "2" for inch and
// "3" for centimeter. In IJG lib, "1" for inch and "2" for
// centimeter.
cinfo->density_unit = 1;
break;
case 3:
// Resolution unit: centimeter
cinfo->density_unit = 2;
break;
default:
// Unknow Resolution unit:
cinfo->density_unit = 0;
}
}
break;
default:
// We don't care the rest tags
break;
}// switch
}// Loop all the entries
lpData = (BYTE*)(((IFD_TAG UNALIGNED *)lpData) + wEntries);
// Get offset to next IFD
if ((int) (lpData - pcSrcData) > (iBytesRemaining +(int)sizeof(UINT32)))
{
// Buffer too small
return E_FAIL;
}
return S_OK;
}// ReadApp1HeaderInfo()
/**************************************************************************\
*
* Function Description:
*
* This function checks a given TAG ID to see if it belongs to the EXIF IFD
* section.
*
* Note: we don't count EXIF_TAG_INTEROP as in this section since it will be
* added based on if we have InterOP ID or not.
*
* Return Value:
*
* Return TRUE if the input ID belongs to EXIF IFD. Otherwise, return FALSE.
*
\**************************************************************************/
BOOL
IsInExifIFDSection(
PROPID id // ID to be checked
)
{
switch (id)
{
case EXIF_TAG_EXPOSURE_TIME:
case EXIF_TAG_F_NUMBER:
case EXIF_TAG_EXPOSURE_PROG:
case EXIF_TAG_SPECTRAL_SENSE:
case EXIF_TAG_ISO_SPEED:
case EXIF_TAG_OECF:
case EXIF_TAG_VER:
case EXIF_TAG_D_T_ORIG:
case EXIF_TAG_D_T_DIGITIZED:
case EXIF_TAG_COMP_CONFIG:
case EXIF_TAG_COMP_BPP:
case EXIF_TAG_SHUTTER_SPEED:
case EXIF_TAG_APERATURE:
case EXIF_TAG_BRIGHTNESS:
case EXIF_TAG_EXPOSURE_BIAS:
case EXIF_TAG_MAX_APERATURE:
case EXIF_TAG_SUBJECT_DIST:
case EXIF_TAG_METERING_MODE:
case EXIF_TAG_LIGHT_SOURCE:
case EXIF_TAG_FLASH:
case EXIF_TAG_FOCAL_LENGTH:
case EXIF_TAG_MAKER_NOTE:
case EXIF_TAG_USER_COMMENT:
case EXIF_TAG_D_T_SUBSEC:
case EXIF_TAG_D_T_ORIG_SS:
case EXIF_TAG_D_T_DIG_SS:
case EXIF_TAG_FPX_VER:
case EXIF_TAG_COLOR_SPACE:
case EXIF_TAG_PIX_X_DIM:
case EXIF_TAG_PIX_Y_DIM:
case EXIF_TAG_RELATED_WAV:
case EXIF_TAG_FLASH_ENERGY:
case EXIF_TAG_SPATIAL_FR:
case EXIF_TAG_FOCAL_X_RES:
case EXIF_TAG_FOCAL_Y_RES:
case EXIF_TAG_FOCAL_RES_UNIT:
case EXIF_TAG_SUBJECT_LOC:
case EXIF_TAG_EXPOSURE_INDEX:
case EXIF_TAG_SENSING_METHOD:
case EXIF_TAG_FILE_SOURCE:
case EXIF_TAG_SCENE_TYPE:
case EXIF_TAG_CFA_PATTERN:
return TRUE;
default:
return FALSE;
}
}// IsInExifIFDSection()
/**************************************************************************\
*
* Function Description:
*
* This function checks a given TAG ID to see if it belongs to the GPS IFD
* section.
*
* Return Value:
*
* Return TRUE if the input ID belongs to GPS IFD. Otherwise, return FALSE.
*
\**************************************************************************/
BOOL
IsInGpsIFDSection(
PROPID id // ID to be checked
)
{
switch (id)
{
case GPS_TAG_VER:
case GPS_TAG_LATTITUDE_REF:
case GPS_TAG_LATTITUDE:
case GPS_TAG_LONGITUDE_REF:
case GPS_TAG_LONGITUDE:
case GPS_TAG_ALTITUDE_REF:
case GPS_TAG_ALTITUDE:
case GPS_TAG_GPS_TIME:
case GPS_TAG_GPS_SATELLITES:
case GPS_TAG_GPS_STATUS:
case GPS_TAG_GPS_MEASURE_MODE:
case GPS_TAG_GPS_DOP:
case GPS_TAG_SPEED_REF:
case GPS_TAG_SPEED:
case GPS_TAG_TRACK_REF:
case GPS_TAG_TRACK:
case GPS_TAG_IMG_DIR_REF:
case GPS_TAG_IMG_DIR:
case GPS_TAG_MAP_DATUM:
case GPS_TAG_DEST_LAT_REF:
case GPS_TAG_DEST_LAT:
case GPS_TAG_DEST_LONG_REF:
case GPS_TAG_DEST_LONG:
case GPS_TAG_DEST_BEAR_REF:
case GPS_TAG_DEST_BEAR:
case GPS_TAG_DEST_DIST_REF:
case GPS_TAG_DEST_DIST:
return TRUE;
default:
return FALSE;
}
}
/**************************************************************************\
*
* Function Description:
*
* This function checks a given TAG ID to see if it should be written after
* TAG_EXIF_IFD and TAG_GPS_IFD in the 0th IFD. This is for sorting purpose.
*
* Return Value:
*
* Return TRUE if the input ID should be written after TAG_EXIF_IFD and
* TAG_GPS_IFD in the 0th IFD. Otherwise, return FALSE.
*
\**************************************************************************/
BOOL
IsInLargeSection(
PROPID id // ID to be checked
)
{
// Of course, if it is a GPS_IFD, we don't need to do so.
if ((id > TAG_EXIF_IFD) && (id != TAG_GPS_IFD) &&
(IsInExifIFDSection(id) == FALSE) &&
(IsInFilterOutSection(id) == FALSE))
{
return TRUE;
}
else
{
return FALSE;
}
}// IsInExifIFDSection()
/**************************************************************************\
*
* Function Description:
*
* This function checks a given TAG ID to see if it belongs to the thumbnail
* section.
*
* Note: since we store thumbnail in JPEG compressed format. So following TAGs
* should not be saved:
* case TAG_THUMBNAIL_IMAGE_WIDTH:
* case TAG_THUMBNAIL_IMAGE_HEIGHT:
* case TAG_THUMBNAIL_BITS_PER_SAMPLE:
* case TAG_THUMBNAIL_PHOTOMETRIC_INTERP:
* case TAG_THUMBNAIL_STRIP_OFFSETS:
* case TAG_THUMBNAIL_SAMPLES_PER_PIXEL:
* case TAG_THUMBNAIL_ROWS_PER_STRIP:
* case TAG_THUMBNAIL_STRIP_BYTES_COUNT:
* case TAG_THUMBNAIL_PLANAR_CONFIG:
* case TAG_THUMBNAIL_YCbCr_COEFFICIENTS:
* case TAG_THUMBNAIL_YCbCr_SUBSAMPLING:
* case TAG_THUMBNAIL_REF_BLACK_WHITE:
* They are all in IsInFilterOutSection()
*
* Return Value:
*
* Return TRUE if the input ID belongs to thumbnail IFD. Otherwise, return
* FALSE.
*
\**************************************************************************/
BOOL
IsInThumbNailSection(
PROPID id // ID to be checked
)
{
switch (id)
{
case TAG_THUMBNAIL_COMPRESSION:
case TAG_THUMBNAIL_IMAGE_DESCRIPTION:
case TAG_THUMBNAIL_EQUIP_MAKE:
case TAG_THUMBNAIL_EQUIP_MODEL:
case TAG_THUMBNAIL_ORIENTATION:
case TAG_THUMBNAIL_RESOLUTION_X:
case TAG_THUMBNAIL_RESOLUTION_Y:
case TAG_THUMBNAIL_RESOLUTION_UNIT:
case TAG_THUMBNAIL_TRANSFER_FUNCTION:
case TAG_THUMBNAIL_SOFTWARE_USED:
case TAG_THUMBNAIL_DATE_TIME:
case TAG_THUMBNAIL_ARTIST:
case TAG_THUMBNAIL_WHITE_POINT:
case TAG_THUMBNAIL_PRIMAY_CHROMATICS:
case TAG_THUMBNAIL_YCbCr_POSITIONING:
case TAG_THUMBNAIL_COPYRIGHT:
return TRUE;
default:
return FALSE;
}
}// IsInThumbNailSection()
/**************************************************************************\
*
* Function Description:
*
* This function filters out TAGs which is not necessary to be written in the
* APP1 header. For example, most of them are GDI+ internal thumbnail TAGs, they
* will be converted to real Exif spec when written out. Also in the list below
* are TAG_EXIF_IFD, TAG_GPS_IFD, EXIF_TAG_INTEROP etc. These tags are written
* based on if we have specific tags under these IFDs.
* Filter out TAG_LUMINANCE_TABLE and TAG_CHROMINANCE_TABLE because these will
* be set in jpeg_set_quality if the user uses its own tables.
* ICC profile should be written to APP2 header, not here.
*
* Return Value:
*
* Return TRUE if the input ID is in the filter out list. Otherwise, return
* FALSE.
*
\**************************************************************************/
BOOL
IsInFilterOutSection(
PROPID id // ID to be checked
)
{
switch (id)
{
case TAG_THUMBNAIL_IMAGE_WIDTH:
case TAG_THUMBNAIL_IMAGE_HEIGHT:
case TAG_THUMBNAIL_BITS_PER_SAMPLE:
case TAG_THUMBNAIL_PHOTOMETRIC_INTERP:
case TAG_THUMBNAIL_STRIP_OFFSETS:
case TAG_THUMBNAIL_SAMPLES_PER_PIXEL:
case TAG_THUMBNAIL_ROWS_PER_STRIP:
case TAG_THUMBNAIL_STRIP_BYTES_COUNT:
case TAG_THUMBNAIL_PLANAR_CONFIG:
case TAG_THUMBNAIL_YCbCr_COEFFICIENTS:
case TAG_THUMBNAIL_YCbCr_SUBSAMPLING:
case TAG_THUMBNAIL_REF_BLACK_WHITE:
case TAG_EXIF_IFD:
case TAG_GPS_IFD:
case TAG_LUMINANCE_TABLE:
case TAG_CHROMINANCE_TABLE:
case EXIF_TAG_INTEROP:
case TAG_JPEG_INTER_FORMAT:
case TAG_ICC_PROFILE:
return TRUE;
default:
return FALSE;
}
}// IsInFilterOutSection()
/**************************************************************************\
*
* Function Description:
*
* This function creates an JPEG APP1 marker (EXIF) in memory, based on input
* PropertyItem list.
*
* Return Value:
*
* Status code
*
* Note:
* During writing to the buffer, we don't need to check if we exceed the bounds
* of the marker buffer or not. This is for performance reason. The caller
* should allocate sufficient memory buffer for this routine
*
* Revision History:
*
* 07/06/2000 MinLiu
* Created it.
*
\**************************************************************************/
HRESULT
CreateAPP1Marker(
IN PropertyItem* pPropertyList,// Input PropertyItem list
IN UINT uiNumOfPropertyItems, // Number of Property items in the input list
IN BYTE *pbMarkerBuffer, // Memory buffer for storing the APP1 header
OUT UINT *puiCurrentLength, // Total bytes written to APP1 header buffer
IN UINT uiTransformation // Transformation info
)
{
if ((pbMarkerBuffer == NULL) || (puiCurrentLength == NULL))
{
WARNING(("EXIF: CreateAPP1Marker failed---Invalid input parameters"));
return E_INVALIDARG;
}
BOOL fRotate = FALSE;
// Check if the user has asked for lossless transformation. If yes, we need
// to do something below.
if ((uiTransformation == EncoderValueTransformRotate90) ||
(uiTransformation == EncoderValueTransformRotate270))
{
fRotate = TRUE;
}
ULONG uiNumOfTagsToWrite = 0; // Number of TAGs needed to be written in
// current IFD
ULONG ulThumbnailLength = 0; // Thumbnail length, in bytes
BYTE *pbThumbBits = NULL; // Pointer to thumbnail bits
UINT uiNumOfExifTags = 0; // Number of EXIF specific TAGs
UINT uiNumOfGpsTags = 0; // Number of GPS specific TAGs
UINT uiNumOfLargeTags = 0; // Number of TAGs larger than TAG_EXIF_IFD
UINT uiNumOfThumbnailTags = 0; // Number of thumbnail TAGs
UINT uiNumOfInterOPTags = 0; // Number of interoperability TAGs
PropertyItem *pItem = pPropertyList;
// Categorize TAGs: Count how many TAGs are really needed to be saved,
// filter out tags which are not necessary to be saved, not supported etc.
for (int i = 0; i < (INT)uiNumOfPropertyItems; ++i)
{
// If the image is rotated, we need to swap all related property items
// by swapping IDs
if (fRotate == TRUE)
{
SwapIDIfNeeded(pItem);
}// Swap ID values if it is rotated
if (IsInExifIFDSection(pItem->id) == TRUE)
{
// We hit an EXIF specific TAG. Need to write it in ExifIFD later
uiNumOfExifTags++;
}
else if (IsInGpsIFDSection(pItem->id) == TRUE)
{
// We hit an GPS specific TAG. Need to write it in GpsIFD later
uiNumOfGpsTags++;
}
else if (IsInInterOPIFDSection(pItem->id) == TRUE)
{
// We hit an EXIF interOP specific TAG. Need to write it in
// ExifIFD's InterOP IFD later
uiNumOfInterOPTags++;
}
else if (IsInLargeSection(pItem->id) == TRUE)
{
// We hit a TAG which has an ID bigger than TAG_EXIF_IFD, like the
// SHELL's Unicode user comments, title tags etc.
// Need to write it after the TAG_EXIF_IFD tag
uiNumOfLargeTags++;
uiNumOfTagsToWrite++;
}
else if (IsInThumbNailSection(pItem->id) == TRUE)
{
// Hit a useful thumbnail TAG. Write it to 1st IFD later
uiNumOfThumbnailTags++;
}
else if (pItem->id == TAG_THUMBNAIL_DATA)
{
pbThumbBits = (BYTE*)pItem->value;
if (pbThumbBits)
{
// Get total thumbnail length
ulThumbnailLength = pItem->length;
}
else
{
WARNING(("Exif---CreateAPP1Marker, NULL thumbnail bits"));
// Set ulThumbnailLength = 0 which means we don't have thumb
ulThumbnailLength = 0;
}
}
else if ((IsInFilterOutSection(pItem->id) == FALSE) &&
(pItem->id != TAG_JPEG_INTER_LENGTH))
{
// Hit a real TAG needs to be written in 0th IFD
// Note: we don't need to count TAG_JPEG_INTER_LENGTH since we will
// treat it specially in writing thumbnail section
uiNumOfTagsToWrite++;
}
// Move onto next item
pItem++;
}// Loop through all the property items to Categorize them
if ((uiNumOfTagsToWrite == 0) && (uiNumOfExifTags == 0) &&
(uiNumOfGpsTags == 0) && (uiNumOfInterOPTags == 0) &&
(uiNumOfThumbnailTags == 0) && (ulThumbnailLength != 0))
{
// If there is nothing to write, just bail out
return S_OK;
}
// Sort all tags based on their ID
SortTags(pPropertyList, uiNumOfPropertyItems);
// If we need to write EXIF or GPS specific tag, we need to allocate one
// entry for each of them
if (uiNumOfExifTags > 0)
{
uiNumOfTagsToWrite++;
}
if (uiNumOfGpsTags > 0)
{
uiNumOfTagsToWrite++;
}
// Write an EXIF header out, aka EXIF Identifier
BYTE *pbCurrent = pbMarkerBuffer;
pbCurrent[0] = 'E';
pbCurrent[1] = 'x';
pbCurrent[2] = 'i';
pbCurrent[3] = 'f';
pbCurrent[4] = 0;
pbCurrent[5] = 0;
UINT uiTotalBytesWritten = 6; // Total bytes written so far
// Write out machine type as "little endian" and "identification" bytes 2A
UINT16 UNALIGNED *pui16Temp = (UINT16 UNALIGNED*)(pbMarkerBuffer +
uiTotalBytesWritten);
pui16Temp[0] = 0x4949;
pui16Temp[1] = 0x2A;
uiTotalBytesWritten += 4;
// Use 4 bytes to write out 0th offset to 0th IFD. Since we write out the
// 0th IFD immediately after the header, so we put 8 here. This means that
// the 0th IFD is 8 bytes after the "little endian" and "offset" field.
UINT32 UNALIGNED *pulIFDOffset = (UINT32 UNALIGNED*)(pbMarkerBuffer +
uiTotalBytesWritten);
*pulIFDOffset = 8;
uiTotalBytesWritten += 4;
// Fill in the "number of entry" field, 2 bytes
UINT16 UNALIGNED *pui16NumEntry = (UINT16 UNALIGNED*)(pbMarkerBuffer +
uiTotalBytesWritten);
*pui16NumEntry = (UINT16)uiNumOfTagsToWrite;
uiTotalBytesWritten += 2;
// We need to create "uiNumOfTagsToWrite" of TAG entries (aka directory
// entries in TIFF's term)
ULONG ulTagSectionLength = sizeof(IFD_TAG) * uiNumOfTagsToWrite;
IFD_TAG *pTagBuf = (IFD_TAG*)GpMalloc(ulTagSectionLength);
if (pTagBuf == NULL)
{
WARNING(("EXIF: CreateAPP1Marker failed---Out of memory"));
return E_OUTOFMEMORY;
}
// We can't write all the TAGs now since we can't fill in all the values at
// this moment. So we have to remember where to write 0th IFD, (pbIFDOffset)
BYTE *pbIFDOffset = pbMarkerBuffer + uiTotalBytesWritten;
// We need to count "ulTagSectionLength" bytes as written. This makes it
// easier for counting the offset below.
// Note: Here "+4" is for the 4 bytes taken for writing the offset for next
// IFD offset. We will fill in the value later
uiTotalBytesWritten += (ulTagSectionLength + 4);
// Figure out the offset for 0th IFD value section
// According to the EXIF spec, "0th IFD value" section should be after "0th
// IFD". So here we need to figure out the offset for that value "pbCurrent"
pbCurrent = pbMarkerBuffer + uiTotalBytesWritten;
pItem = pPropertyList; // Let pItem points to the beginning of
// PropertyItem buffer
IFD_TAG *pCurrentTag = NULL; // Current tag to write to memory buffer
ULONG uiNumOfTagsWritten = 0; // Counter of TAGs written
HRESULT hr = S_OK;
// Write 0th IFD
for (int i = 0; i < (INT)uiNumOfPropertyItems; ++i)
{
MakeOffsetEven(uiTotalBytesWritten);
// Filter out tags which are not necessary to be saved in 0th IFD at
// this moment
if ( (IsInFilterOutSection(pItem->id) == TRUE)
||(IsInThumbNailSection(pItem->id) == TRUE)
||(IsInExifIFDSection(pItem->id) == TRUE)
||(IsInGpsIFDSection(pItem->id) == TRUE)
||(IsInLargeSection(pItem->id) == TRUE)
||(IsInInterOPIFDSection(pItem->id) == TRUE)
||(pItem->id == TAG_JPEG_INTER_LENGTH)
||(pItem->id == TAG_THUMBNAIL_DATA) )
{
// Move onto next PropertyItem
pItem++;
continue;
}
// Hit a TAG which needs to be saved in 0th IFD. So fill out a new TAG
// structure
pCurrentTag = pTagBuf + uiNumOfTagsWritten;
hr = WriteATag(
pbMarkerBuffer,
pCurrentTag,
pItem,
&pbCurrent,
&uiTotalBytesWritten
);
if (FAILED(hr))
{
WARNING(("EXIF: CreateAPP1Marker--WriteATag() failed"));
break;
}
uiNumOfTagsWritten++;
// Move onto next PropertyItem
pItem++;
}// Write 0th IFD
if (SUCCEEDED(hr))
{
// Check if we need to write EXIF IFD or not
UINT UNALIGNED *pExifIFDOffset = NULL; // Pointer to remember Exif IFD
// offset
if (uiNumOfExifTags > 0)
{
// Find the memory location for storing Exif TAG
pCurrentTag = pTagBuf + uiNumOfTagsWritten;
// Fill out an EXIF IFD Tag
pCurrentTag->wTag = TAG_EXIF_IFD;
pCurrentTag->wType = TAG_TYPE_LONG;
pCurrentTag->dwCount = 1;
// Set the offset for specific EXIF IFD entry
pCurrentTag->dwOffset = uiTotalBytesWritten - 6;
// This "offset" might get changed if there is any "large tag" needs
// to be written. So remember the address now so we can update it
// later.
pExifIFDOffset = (UINT UNALIGNED*)(&(pCurrentTag->dwOffset));
uiNumOfTagsWritten++;
}
UINT UNALIGNED *pGpsIFDOffset = NULL; // Pointer to remember Gps IFD
// offset
if (uiNumOfGpsTags > 0)
{
// Find the memory location for storing Gps TAG
pCurrentTag = pTagBuf + uiNumOfTagsWritten;
// Fill out an GPS IFD Tag
pCurrentTag->wTag = TAG_GPS_IFD;
pCurrentTag->wType = TAG_TYPE_LONG;
pCurrentTag->dwCount = 1;
// Set the offset for specific GPS IFD entry
pCurrentTag->dwOffset = uiTotalBytesWritten - 6;
// This "offset" might get changed if there is any "large tag" needs
// to be written. So remember the address now so we can update it
// later.
pGpsIFDOffset = (UINT UNALIGNED*)(&(pCurrentTag->dwOffset));
uiNumOfTagsWritten++;
}
// Check if we need to write any tags after TAG_EXIF_IFD id
if (uiNumOfLargeTags > 0)
{
pItem = pPropertyList;
for (int i = 0; i < (INT)uiNumOfPropertyItems; ++i)
{
MakeOffsetEven(uiTotalBytesWritten);
if (IsInLargeSection(pItem->id) == TRUE)
{
// Hit a large TAG. Fill out a new TAG structure
pCurrentTag = pTagBuf + uiNumOfTagsWritten;
hr = WriteATag(
pbMarkerBuffer,
pCurrentTag,
pItem,
&pbCurrent,
&uiTotalBytesWritten
);
if (FAILED(hr))
{
WARNING(("EXIF: CreateAPP1Marker--WriteATag() failed"));
break;
}
uiNumOfTagsWritten++;
}
// Move onto next PropertyItem
pItem++;
}// Loop through all the property items to write large TAGs
// Adjust the ExifIFDOffset pointer if necessary
if (SUCCEEDED(hr) && pExifIFDOffset)
{
*pExifIFDOffset = (uiTotalBytesWritten - 6);
}
if (SUCCEEDED(hr) && pGpsIFDOffset)
{
*pGpsIFDOffset = (uiTotalBytesWritten - 6);
}
}// If we need to write TAGs after TAG_EXIF_IFD
if (SUCCEEDED(hr))
{
// These two numbers should be identical. Assert here in case we
// messed up the categorizing and writing above
ASSERT(uiNumOfTagsWritten == uiNumOfTagsToWrite);
// Now fill the EXIF specific IFD if necessary
if (uiNumOfExifTags > 0)
{
hr = WriteExifIFD(
pbMarkerBuffer,
pPropertyList,
uiNumOfPropertyItems,
uiNumOfExifTags,
uiNumOfInterOPTags,
&uiTotalBytesWritten
);
if (SUCCEEDED(hr) && pGpsIFDOffset)
{
*pGpsIFDOffset = (uiTotalBytesWritten - 6);
}
}// Write EXIF specific IFD
if (SUCCEEDED(hr))
{
// Now fill the GPS specific IFD if necessary
if (uiNumOfGpsTags > 0)
{
hr = WriteGpsIFD(
pbMarkerBuffer,
pPropertyList,
uiNumOfPropertyItems,
uiNumOfGpsTags,
&uiTotalBytesWritten
);
}// Write GPS specific IFD
if (SUCCEEDED(hr))
{
// After the above loop, we have filled all the fields in
// all the TAG structures. Write out all the directory
// entries now
GpMemcpy(pbIFDOffset, (BYTE*)pTagBuf, ulTagSectionLength);
// Set the offset to next IFD
pbCurrent = pbIFDOffset + ulTagSectionLength;
pulIFDOffset = (UINT32 UNALIGNED*)pbCurrent;
// Check if we need to write out the thumbnail, aka 1st IFD
if ((ulThumbnailLength != 0) && (pbThumbBits != NULL))
{
// Offset has to be an even number
MakeOffsetEven(uiTotalBytesWritten);
// Fill the offset value in 0th IFD offset field to
// point it to 1st IFD
*pulIFDOffset = (uiTotalBytesWritten - 6);
BYTE *pbDummy = (BYTE*)pulIFDOffset;
hr = Write1stIFD(
pbMarkerBuffer,
pPropertyList,
uiNumOfPropertyItems,
uiNumOfThumbnailTags,
ulThumbnailLength,
pbThumbBits,
&pbDummy,
&uiTotalBytesWritten
);
pulIFDOffset = (UINT32 UNALIGNED*)pbDummy;
}
if (SUCCEEDED(hr))
{
// Set the next IFD offset to NULL to terminate the page
*pulIFDOffset = 0;
*puiCurrentLength = uiTotalBytesWritten;
}
}
}
}
}
if (pTagBuf)
{
GpFree(pTagBuf);
}
return hr;
}// CreateAPP1Marker()
/**************************************************************************\
*
* Function Description:
*
* This function uses SHELL sort to sort TAGs based on their ID
*
* Return Value:
*
* None.
*
\**************************************************************************/
void
SortTags(
PropertyItem *pItemBuffer,
UINT cPropertyItems
)
{
PropertyItem tempTag;
PropertyItem *pTag = pItemBuffer;
// Shell sort
for (int step = (cPropertyItems >> 1); step > 0; step >>= 1 )
{
for (int i = step; i < (int)cPropertyItems; i++)
{
for (int j = i - step; j >= 0; j-= step )
{
if (pTag[j].id < pTag[j+1].id)
{
break;
}
tempTag = pTag[j];
pTag[j] = pTag[j + step];
pTag[j + step] = tempTag;
}
}
}
return;
}// SortTags()
/**************************************************************************\
*
* Function Description:
*
* This function is used to swap the a TAG id when rotate 90/270 degree
*
* Return Value:
*
* None.
*
\**************************************************************************/
void
SwapIDIfNeeded(
PropertyItem *pItem
)
{
switch (pItem->id)
{
case PropertyTagImageWidth:
pItem->id = PropertyTagImageHeight;
break;
case PropertyTagImageHeight:
pItem->id = PropertyTagImageWidth;
break;
case PropertyTagXResolution:
pItem->id = PropertyTagYResolution;
break;
case PropertyTagYResolution:
pItem->id = PropertyTagXResolution;
break;
case PropertyTagResolutionXUnit:
pItem->id = PropertyTagResolutionYUnit;
break;
case PropertyTagResolutionYUnit:
pItem->id = PropertyTagResolutionXUnit;
break;
case PropertyTagResolutionXLengthUnit:
pItem->id = PropertyTagResolutionYLengthUnit;
break;
case PropertyTagResolutionYLengthUnit:
pItem->id = PropertyTagResolutionXLengthUnit;
break;
case PropertyTagExifPixXDim:
pItem->id = PropertyTagExifPixYDim;
break;
case PropertyTagExifPixYDim:
pItem->id = PropertyTagExifPixXDim;
break;
case PropertyTagExifFocalXRes:
pItem->id = PropertyTagExifFocalYRes;
break;
case PropertyTagExifFocalYRes:
pItem->id = PropertyTagExifFocalXRes;
break;
case PropertyTagThumbnailResolutionX:
pItem->id = PropertyTagThumbnailResolutionY;
break;
case PropertyTagThumbnailResolutionY:
pItem->id = PropertyTagThumbnailResolutionX;
break;
default:
// For rest of property IDs, no need to swap
break;
}
return;
}
/**************************************************************************\
*
* Function Description:
*
* This function writes out a TAG
*
* Return Value:
*
* Status code.
*
\**************************************************************************/
HRESULT
WriteATag(
BYTE *pbMarkerBuffer, // Pointer to marker buffer for IFD
IFD_TAG *pCurrentTag, // Current TAG
PropertyItem *pTempItem, // Property item
BYTE **ppbCurrent, // Position
UINT *puiTotalBytesWritten // Total bytes written
)
{
HRESULT hr = S_OK;
pCurrentTag->wTag = (WORD)pTempItem->id;
// NOTE: there is A difference between "IFD_TAG.dwCount" and
// PropertyItem.length
// "IFD_TAG.dwCount" means the number of values. IT IS NOT THE SUM OF
// THE BYTES
// "PropertyItem.length" is "Length of the property value, in bytes"
// So we need to do some convertion here
pCurrentTag->dwCount = pTempItem->length;
pCurrentTag->wType = pTempItem->type;
switch (pCurrentTag->wType)
{
case TAG_TYPE_ASCII:
case TAG_TYPE_BYTE:
case TAG_TYPE_UNDEFINED:
pCurrentTag->dwCount = pTempItem->length;
if (pCurrentTag->dwCount > 4)
{
// Write to the current mark buffer and remember the offset
GpMemcpy(*ppbCurrent, (BYTE*)pTempItem->value, pTempItem->length);
// Here "-6" is because the offset starts at 6 bytes after
// the "Exif ", EXIF signature
pCurrentTag->dwOffset = *puiTotalBytesWritten - 6;
*puiTotalBytesWritten += pTempItem->length;
MakeOffsetEven(*puiTotalBytesWritten);
*ppbCurrent = pbMarkerBuffer + (*puiTotalBytesWritten);
}
else
{
// Write to the current mark buffer and remember the offset
GpMemcpy(
&pCurrentTag->dwOffset,
(BYTE*)pTempItem->value,
pTempItem->length
);
}
break;
case TAG_TYPE_RATIONAL:
case TAG_TYPE_SRATIONAL:
pCurrentTag->dwCount = (pTempItem->length >> 3);
// Write to the current mark buffer and remember the offset
GpMemcpy(*ppbCurrent, (BYTE*)pTempItem->value, pTempItem->length);
// Here "-6" is because the offset starts at 6 bytes after the
// "Exif ", EXIF signature
pCurrentTag->dwOffset = (*puiTotalBytesWritten - 6);
*puiTotalBytesWritten += pTempItem->length;
MakeOffsetEven(*puiTotalBytesWritten);
*ppbCurrent = pbMarkerBuffer + (*puiTotalBytesWritten);
break;
case TAG_TYPE_SHORT:
pCurrentTag->dwCount = (pTempItem->length >> 1);
if (pCurrentTag->dwCount == 1)
{
pCurrentTag->us = *((UINT16 UNALIGNED*)pTempItem->value);
}
else
{
// We have to write the value to the offset field
GpMemcpy(*ppbCurrent, (BYTE*)pTempItem->value, pTempItem->length);
// Here "-6" is because the offset starts at 6 bytes after
// the "Exif ", EXIF signature
pCurrentTag->dwOffset = *puiTotalBytesWritten - 6;
*puiTotalBytesWritten += pTempItem->length;
MakeOffsetEven(*puiTotalBytesWritten);
*ppbCurrent = pbMarkerBuffer + (*puiTotalBytesWritten);
}
break;
case TAG_TYPE_LONG:
case TAG_TYPE_SLONG:
pCurrentTag->dwCount = (pTempItem->length >> 2);
if (pCurrentTag->dwCount == 1)
{
pCurrentTag->l = *((INT32 UNALIGNED*)pTempItem->value);
}
else
{
// We have to write the value to the offset field
GpMemcpy(*ppbCurrent, (BYTE*)pTempItem->value, pTempItem->length);
// Here "-6" is because the offset starts at 6 bytes after
// the "Exif ", EXIF signature
pCurrentTag->dwOffset = (*puiTotalBytesWritten - 6);
*puiTotalBytesWritten += pTempItem->length;
MakeOffsetEven(*puiTotalBytesWritten);
*ppbCurrent = pbMarkerBuffer + (*puiTotalBytesWritten);
}
break;
default:
WARNING(("EXIF: WriteExifHeader---Unknown property type"));
hr = E_FAIL;
}// switch on type
return hr;
}
/**************************************************************************\
*
* Function Description:
*
* This function writes out all thumbnail tags
*
* Return Value:
*
* Status code.
*
\**************************************************************************/
HRESULT
WriteThumbnailTags(
IN PropertyItem *pItemBuffer, // Property item list
IN BYTE *pbMarkerBuffer, // Pointer to marker buffer for IFD
IN IFD_TAG *pTags, // TAG to be written
IN UINT uiNumOfPropertyItems, // Number of property items
IN OUT UINT *puiNumOfThumbnailTagsWritten, // Num of thumbnail tags written
IN OUT UINT *puiTotalBytesWritten, // Total bytes written so far
IN BOOL fWriteSmallTag // TRUE if to write TAG whose ID is
//smaller than TAG_JPEG_INTER_FORMAT
)
{
PropertyItem *pItem = pItemBuffer;
// Figure out the offset for 1st IFD value section
BYTE *pbCurrent = pbMarkerBuffer + (*puiTotalBytesWritten);
IFD_TAG *pCurrentTag = NULL;
HRESULT hr = S_OK;
for (int i = 0; i < (INT)uiNumOfPropertyItems; ++i)
{
// Only write Thumbnail specific TAGs
if (IsInThumbNailSection(pItem->id) == TRUE)
{
// Need to copy the property item first since we don't want to
// values in the original property item.
PropertyItem tempItem;
CopyPropertyItem(pItem, &tempItem);
// Map all GDI+ internal thumbnail TAG IDs to EXIF defined tag IDs.
ThumbTagToMainImgTag(&tempItem);
if (((fWriteSmallTag == TRUE) &&
(tempItem.id < TAG_JPEG_INTER_FORMAT)) ||
((fWriteSmallTag == FALSE) &&
(tempItem.id > TAG_JPEG_INTER_LENGTH)))
{
// Fill out a new TAG structure
pCurrentTag = pTags + (*puiNumOfThumbnailTagsWritten);
hr = WriteATag(
pbMarkerBuffer,
pCurrentTag,
&tempItem,
&pbCurrent,
puiTotalBytesWritten
);
if (FAILED(hr))
{
WARNING(("WriteThumbnailTags failed--WriteATag failed"));
break;
}
*puiNumOfThumbnailTagsWritten += 1;
}
}
// Move onto next PropertyItem
pItem++;
}// Loop through all the property items to write EXIF tags
return hr;
}
/**************************************************************************\
*
* Function Description:
*
* This function writes out the EXIF IFD.
*
* Return Value:
*
* Status code.
*
\**************************************************************************/
HRESULT
WriteExifIFD(
IN BYTE *pbMarkerBuffer, // Pointer to marker buffer for IFD
IN PropertyItem *pItemBuffer, // Property item list
IN UINT uiNumOfPropertyItems, // Number of property items
IN UINT uiNumOfExifTags, // Number of Exif property items
IN UINT uiNumOfInterOPTags, // Number of InterOP property items
IN OUT UINT *puiTotalBytesWritten // Total bytes written
)
{
// ISSUE-2002/02/04--minliu, Due to the lack of spec for InterOpbility, we
// don't want to write InterOP IFD. So this line below gurantees that we
// won't write this IFD. We will take this line out in GDI+ V2 if we feel
// we need to support it.
uiNumOfInterOPTags = 0;
if (uiNumOfInterOPTags > 0)
{
// If we see InterOP tags, we need to add one entry in the exif IFD
uiNumOfExifTags++;
}
// Fill in the number of entry field, 2 bytes
UINT16 UNALIGNED *pui16NumEntry = (UINT16 UNALIGNED*)(pbMarkerBuffer +
(*puiTotalBytesWritten));
*pui16NumEntry = (UINT16)uiNumOfExifTags;
*puiTotalBytesWritten += 2;
UINT uiExifTagSectionLength = sizeof(IFD_TAG) * uiNumOfExifTags;
IFD_TAG *pTagBuf = (IFD_TAG*)GpMalloc(uiExifTagSectionLength);
if (pTagBuf == NULL)
{
WARNING(("EXIF: WriteExifHeader failed---Out of memory"));
return E_OUTOFMEMORY;
}
// Remember where to write EXIF IFD, (pbExifIFDOffset). We can't write
// all the TAGs now since we can't fill in all the values at this moment
BYTE *pbExifIFDOffset = pbMarkerBuffer + (*puiTotalBytesWritten);
// We need to count "uiExifTagSectionLength" bytes as written. This
// makes it easier for counting the offset below.
// Here "+4" is for 4 bytes for writing next IFD offset.
*puiTotalBytesWritten += (uiExifTagSectionLength + 4);
MakeOffsetEven(*puiTotalBytesWritten);
// Figure out the offset for EXIF IFD value section
BYTE *pbCurrent = pbMarkerBuffer + (*puiTotalBytesWritten);
PropertyItem *pItem = pItemBuffer;
IFD_TAG *pCurrentTag = NULL;
UINT cExifTagsWritten = 0; // Num of EXIF tags have been written so far
UINT cLargeTag = 0;
HRESULT hr = S_OK;
for (int i = 0; i < (INT)uiNumOfPropertyItems; ++i)
{
// Only write EXIF specific TAGs
if (IsInExifIFDSection(pItem->id) == TRUE)
{
// Find an EXIF tag. Need to figure out if its TAG id is bigger than
// InterOP tag or not on condition if we need to write InterOP tag
if ((uiNumOfInterOPTags > 0) && (pItem->id > EXIF_TAG_INTEROP))
{
// Rememeber we have hit a tag whose ID is > EXIF_TAG_INTEROP
// We have to write this TAG after InterOP IFD
cLargeTag++;
}
else
{
// Fill out a new TAG structure
pCurrentTag = pTagBuf + cExifTagsWritten;
hr = WriteATag(
pbMarkerBuffer,
pCurrentTag,
pItem,
&pbCurrent,
puiTotalBytesWritten
);
if (FAILED(hr))
{
WARNING(("EXIF: WriteExifHeader failed--WriteATag failed"));
break;
}
cExifTagsWritten++;
}
}
// Move onto next PropertyItem
pItem++;
}// Loop through all the property items to write EXIF tags
if (SUCCEEDED(hr))
{
// It's time to write InterOP IFD if necessary
// Pointer to remember InterOP IFD offset
UINT UNALIGNED *pInterOPIFDOffset = NULL;
if (uiNumOfInterOPTags > 0)
{
// Find the memory location for storing InterOP TAG
pCurrentTag = pTagBuf + cExifTagsWritten;
// Fill out an InterOP IFD Tag
pCurrentTag->wTag = EXIF_TAG_INTEROP;
pCurrentTag->wType = TAG_TYPE_LONG;
pCurrentTag->dwCount = 1;
// Set the offset for specific InterOP IFD entry
pCurrentTag->dwOffset = (*puiTotalBytesWritten - 6);
// This "offset" might get changed if there is any "large tag" needs to
// be written. So remember the address now so we can update it later.
pInterOPIFDOffset = (UINT UNALIGNED*)(&(pCurrentTag->dwOffset));
cExifTagsWritten++;
}
// Write any TAGs whose id > EXIF_TAG_INTEROP, if there is any
if (cLargeTag > 0)
{
pItem = pItemBuffer;
for (int i = 0; i < (INT)uiNumOfPropertyItems; ++i)
{
MakeOffsetEven(*puiTotalBytesWritten);
if ((IsInExifIFDSection(pItem->id) == TRUE) &&
(pItem->id > EXIF_TAG_INTEROP))
{
// Hit a large TAG. Fill out a new TAG structure
pCurrentTag = pTagBuf + cExifTagsWritten;
hr = WriteATag(
pbMarkerBuffer,
pCurrentTag,
pItem,
&pbCurrent,
puiTotalBytesWritten
);
if (FAILED(hr))
{
WARNING(("WriteExifHeader failed--WriteATag failed"));
break;
}
cExifTagsWritten++;
}
// Move onto next PropertyItem
pItem++;
}// Loop through all the property items to write large TAGs
// Adjust the ExifIFDOffset pointer if necessary
if (SUCCEEDED(hr) && pInterOPIFDOffset)
{
*pInterOPIFDOffset = (*puiTotalBytesWritten - 6);
}
}
if (SUCCEEDED(hr))
{
// After the above loop, we have fill all the fields in all the EXIF
// TAG structure. Write out all the directory entries now
GpMemcpy(pbExifIFDOffset, (BYTE*)pTagBuf, uiExifTagSectionLength);
// Now fill the InterOP IFD if necessary
if (uiNumOfInterOPTags > 0)
{
hr = WriteInterOPIFD(
pbMarkerBuffer,
pItemBuffer,
uiNumOfPropertyItems,
uiNumOfInterOPTags,
puiTotalBytesWritten
);
}// Write EXIF specific IFD
// Add a NULL at the end to terminate the EXIF offset.
*((UINT32 UNALIGNED*)(pbExifIFDOffset + uiExifTagSectionLength)) =
NULL;
}
}
GpFree(pTagBuf);
return hr;
}
/**************************************************************************\
*
* Function Description:
*
* This function writes out the GPS IFD.
*
* Return Value:
*
* Status code.
*
\**************************************************************************/
HRESULT
WriteGpsIFD(
IN BYTE *pbMarkerBuffer, // Pointer to marker buffer for IFD
IN PropertyItem *pItemBuffer, // Property item list
IN UINT uiNumOfPropertyItems, // Number of property items
IN UINT uiNumOfGpsTags, // Number of GPS tags
IN OUT UINT *puiTotalBytesWritten // Total bytes written in the marker buf
)
{
if (uiNumOfGpsTags < 1)
{
// Nothing needs to be written
return S_OK;
}
HRESULT hr = S_OK;
// Fill in the number of entry field, 2 bytes
UINT16 UNALIGNED *pui16NumEntry = (UINT16 UNALIGNED*)(pbMarkerBuffer +
(*puiTotalBytesWritten));
*pui16NumEntry = (UINT16)uiNumOfGpsTags;
*puiTotalBytesWritten += 2;
UINT uiGpsTagSectionLength = sizeof(IFD_TAG) * uiNumOfGpsTags;
IFD_TAG *pTagBuf = (IFD_TAG*)GpMalloc(uiGpsTagSectionLength);
if (pTagBuf == NULL)
{
WARNING(("EXIF: WriteGpsHeader failed---Out of memory"));
return E_OUTOFMEMORY;
}
// Remember where to write GPS IFD, (pbGPSIFDOffset). We can't write
// all the TAGs now since we can't fill in all the values at this moment
BYTE *pbGpsIFDOffset = pbMarkerBuffer + (*puiTotalBytesWritten);
// We need to count "uiGpsTagSectionLength" bytes as written. This
// makes it easier for counting the offset below.
// Here "+4" is for 4 bytes for writing next IFD offset.
*puiTotalBytesWritten += (uiGpsTagSectionLength + 4);
MakeOffsetEven(*puiTotalBytesWritten);
// Figure out the offset for GPS IFD value section
BYTE *pbCurrent = pbMarkerBuffer + (*puiTotalBytesWritten);
PropertyItem *pItem = pItemBuffer;
IFD_TAG *pCurrentTag = NULL;
UINT cGpsTagsWritten = 0; // Num of GPS tags have been written so far
for (int i = 0; i < (INT)uiNumOfPropertyItems; ++i)
{
// Only write GPS specific TAGs
if (IsInGpsIFDSection(pItem->id) == TRUE)
{
// Fill out a new TAG structure
pCurrentTag = pTagBuf + cGpsTagsWritten;
hr = WriteATag(
pbMarkerBuffer,
pCurrentTag,
pItem,
&pbCurrent,
puiTotalBytesWritten
);
if (FAILED(hr))
{
WARNING(("EXIF: WriteGpsHeader---WriteATag() failed"));
break;
}
cGpsTagsWritten++;
}
// Move onto next PropertyItem
pItem++;
}// Loop through all the property items to write GPS tags
if (SUCCEEDED(hr))
{
// After the above loop, we have fill all the fields in all the GPS TAG
// structure. Write out all the directory entries now
GpMemcpy(pbGpsIFDOffset, (BYTE*)pTagBuf, uiGpsTagSectionLength);
// Add a NULL at the end to terminate the GPS offset.
*((UINT32 UNALIGNED*)(pbGpsIFDOffset + uiGpsTagSectionLength)) = NULL;
}
GpFree(pTagBuf);
return hr;
}
/**************************************************************************\
*
* Function Description:
*
* This function writes out the 1st IFD.
*
* Return Value:
*
* Status code.
*
\**************************************************************************/
HRESULT
Write1stIFD(
IN BYTE *pbMarkerBuffer, // Pointer to marker buffer for IFD
IN PropertyItem *pItemBuffer, // Property item list
IN UINT uiNumOfPropertyItems, // Number of property items
IN ULONG uiNumOfThumbnailTags, // Number of thumbnail tags
IN ULONG ulThumbnailLength, // Thumbnail length
IN BYTE *pbThumbBits, // Thumbnail bits
IN OUT BYTE **ppbIFDOffset, // Pointer of "Offset to next IFD"
IN OUT UINT *puiTotalBytesWritten // Total bytes written in the marker buf
)
{
if (uiNumOfThumbnailTags < 1)
{
// Nothing needs to be written
return S_OK;
}
HRESULT hr = S_OK;
// We will write out 2 TAGs for 1st IFD, InterLength and InterFormat
// plus some extra IDs
BYTE *pbCurrent = pbMarkerBuffer + (*puiTotalBytesWritten);
UINT16 UNALIGNED *pui16NumEntry = (UINT16 UNALIGNED*)pbCurrent;
*pui16NumEntry = (UINT16)(2 + uiNumOfThumbnailTags);
*puiTotalBytesWritten += 2;
ULONG ulTagSectionLength = sizeof(IFD_TAG) * (2 + uiNumOfThumbnailTags);
IFD_TAG *pTagBuf = (IFD_TAG*)GpMalloc(ulTagSectionLength);
if (pTagBuf == NULL)
{
WARNING(("EXIF: WriteExifHeader failed---Out of memory"));
return E_OUTOFMEMORY;
}
// Remember where to write 1th IFD, (pbIFDOffset).
BYTE *pbIFDOffset = pbMarkerBuffer + (*puiTotalBytesWritten);
// We need to count "ulTagSectionLength" bytes as written. This is
// easier for counting the offset below
// Note: Here "+4" is for the 4 bytes taken for writing the offset for
// next IFD offset. We will fill the value later
*puiTotalBytesWritten += (ulTagSectionLength + 4);
UINT uiNumOfThumbnailTagsWritten = 0;
// Write thumbnail items with TAGs smaller than JPEG tag
hr = WriteThumbnailTags(
pItemBuffer,
pbMarkerBuffer,
pTagBuf,
uiNumOfPropertyItems,
&uiNumOfThumbnailTagsWritten,
puiTotalBytesWritten,
TRUE // Write TAGs smaller than JPEG tag
);
if (SUCCEEDED(hr))
{
// Fill in 2 thumbnail data TAGs
IFD_TAG *pCurrentTag = pTagBuf + uiNumOfThumbnailTagsWritten;
pCurrentTag->wTag = TAG_JPEG_INTER_FORMAT;
pCurrentTag->wType = TAG_TYPE_LONG;
pCurrentTag->dwCount = 1;
pCurrentTag->dwOffset = (*puiTotalBytesWritten - 6);
uiNumOfThumbnailTagsWritten++;
pCurrentTag = pTagBuf + uiNumOfThumbnailTagsWritten;
pCurrentTag->wTag = TAG_JPEG_INTER_LENGTH;
pCurrentTag->wType = TAG_TYPE_LONG;
pCurrentTag->dwCount = 1;
pCurrentTag->ul = ulThumbnailLength;
uiNumOfThumbnailTagsWritten++;
// Write thumbnail items with TAGs bigger than JPEG tag
hr = WriteThumbnailTags(
pItemBuffer,
pbMarkerBuffer,
pTagBuf,
uiNumOfPropertyItems,
&uiNumOfThumbnailTagsWritten,
puiTotalBytesWritten,
FALSE // Write TAGs bigger than JPEG tag
);
if (SUCCEEDED(hr))
{
// Write out all the directory entries for 1st IFD now
GpMemcpy(pbIFDOffset, (BYTE*)pTagBuf, ulTagSectionLength);
// Set the offset to next IFD
pbCurrent = pbIFDOffset + ulTagSectionLength;
*ppbIFDOffset = pbCurrent;
// Figure out the offset for 1st IFD value section
pbCurrent = pbMarkerBuffer + (*puiTotalBytesWritten);
// Write the thumbnail bits now
GpMemcpy(pbCurrent, pbThumbBits, ulThumbnailLength);
*puiTotalBytesWritten += ulThumbnailLength;
}
}
GpFree(pTagBuf);
return hr;
}
/**************************************************************************\
*
* Function Description:
*
* This function extracts a TIFF thumbnail from the exif header
*
* Note: it is the caller's responsibility to free the memory in "thumbImage" if
* this function return S_OK.
*
* Return Value:
*
* Status code.
*
\**************************************************************************/
HRESULT
DecodeTiffThumbnail(
IN BYTE *pApp1Data, // Base address for APP1 chunk
IN BYTE *pIFD1, // Base address for IFD 1
IN BOOL fBigEndian, // Flag for endian info
IN INT nApp1Length, // Length of APP1 chunk
OUT IImage **thumbImage // Result thumbnail image
)
{
HRESULT hr = E_FAIL;
if (*thumbImage)
{
WARNING(("DecodeTiffThumbnail called when thumbnail already created"));
return S_OK;
}
UINT16 cEntry = *(UINT16 UNALIGNED*)pIFD1;
if (fBigEndian)
{
cEntry = SWAP_WORD(cEntry);
}
// Move the IFD pointer 2 bytes for the "entry field"
pIFD1 += sizeof(UINT16);
IFD_TAG UNALIGNED *pTag = (IFD_TAG UNALIGNED*)pIFD1;
UINT nWidth = 0; // Thumbnail width
UINT nHeight = 0; // Thumbnail height
UINT16 u16PhtoInterpo = 0; // Photometric interpretation
UINT16 u16Compression = 0; // Compression flag
UINT16 u16PlanarConfig = 1; // Default planar-config is 1, aka interleaving
UINT16 u16SubHoriz = 0; // Horizontal sub-sampling
UINT16 u16SubVert = 0; // Vertical sub-sampling
UINT16 u16YCbCrPos = 0; // YCbCr position
float rLumRed = 0.0f; // YCbCr coefficient for RED
float rLumGreen = 0.0f; // YCbCr coefficient for GREEN
float rLumBlue = 0.0f; // YCbCr coefficient for BLUE
float rYLow = 0.0f; // YCbCr Reference: Y black
float rYHigh = 0.0f; // YCbCr Reference: Y white
float rCbLow = 0.0f; // YCbCr Reference: Cb black
float rCbHigh = 0.0f; // YCbCr Reference: Cr white
float rCrLow = 0.0f; // YCbCr Reference: Cr black
float rCrHigh = 0.0f; // YCbCr Reference: Cr white
BYTE *pBits = NULL; // Bits to thumbnail data
// Loop through all the TAGs to extract thumbnail related info
for (INT i = 0; i < cEntry; i++)
{
pTag = ((IFD_TAG UNALIGNED*)pIFD1) + i;
// Check if we have read outside of the APP1 buffer
if (((BYTE*)pTag + sizeof(IFD_TAG)) > (pApp1Data + nApp1Length))
{
WARNING(("DecodeTiffThumbnail read TAG value outside of boundary"));
return E_FAIL;
}
IFD_TAG tNewTag;
if (fBigEndian)
{
tNewTag = SwapIFD_TAG(pTag);
pTag = &tNewTag;
}
switch (pTag->wTag)
{
case TAG_COMPRESSION:
case TAG_THUMBNAIL_COMPRESSION:
if (pTag->wType == TAG_TYPE_SHORT)
{
u16Compression = pTag->us;
}
break;
case TAG_IMAGE_WIDTH:
case TAG_THUMBNAIL_IMAGE_WIDTH:
if (pTag->wType == TAG_TYPE_LONG)
{
nWidth = pTag->ul;
}
else if (pTag->wType == TAG_TYPE_SHORT)
{
// Note: Image width can be LONG or SHORT
nWidth = pTag->us;
}
break;
case TAG_IMAGE_HEIGHT:
case TAG_THUMBNAIL_IMAGE_HEIGHT:
if (pTag->wType == TAG_TYPE_LONG)
{
nHeight = pTag->ul;
}
else if (pTag->wType == TAG_TYPE_SHORT)
{
// Note: Image height can be LONG or SHORT
nHeight = pTag->us;
}
break;
case TAG_STRIP_OFFSETS:
case TAG_THUMBNAIL_STRIP_OFFSETS:
{
int nOffset = 0;
if (pTag->wType == TAG_TYPE_LONG)
{
nOffset = pTag->dwOffset;
}
else if (pTag->wType == TAG_TYPE_SHORT)
{
// Note: Strip offset can be LONG or SHORT
nOffset = pTag->us;
}
// Double check if the offset is valid
if ((nOffset > 0) && (nOffset < nApp1Length))
{
// Offset for data bits has to be within our memory buffer range
pBits = pApp1Data + nOffset;
}
}
break;
case TAG_PLANAR_CONFIG:
case TAG_THUMBNAIL_PLANAR_CONFIG:
if (pTag->wType == TAG_TYPE_SHORT)
{
u16PlanarConfig = pTag->us;
}
break;
case TAG_PHOTOMETRIC_INTERP:
case TAG_THUMBNAIL_PHOTOMETRIC_INTERP:
if (pTag->wType == TAG_TYPE_SHORT)
{
u16PhtoInterpo = pTag->us;
}
break;
case TAG_YCbCr_COEFFICIENTS:
case TAG_THUMBNAIL_YCbCr_COEFFICIENTS:
if ((pTag->wType == TAG_TYPE_RATIONAL) && (pTag->dwCount == 3))
{
int UNALIGNED *piValue = (int UNALIGNED*)(pApp1Data +
pTag->dwOffset);
rLumRed = GetValueFromRational(piValue, fBigEndian);
piValue += 2;
rLumGreen = GetValueFromRational(piValue, fBigEndian);
piValue += 2;
rLumBlue = GetValueFromRational(piValue, fBigEndian);
}
break;
case TAG_YCbCr_SUBSAMPLING:
case TAG_THUMBNAIL_YCbCr_SUBSAMPLING:
if ((pTag->wType == TAG_TYPE_SHORT) && (pTag->dwCount == 2))
{
u16SubHoriz = (UINT16)(pTag->ul & 0x0000ffff);
u16SubVert = (UINT16)((pTag->ul & 0x00ff0000) >> 16);
}
break;
case TAG_YCbCr_POSITIONING:
case TAG_THUMBNAIL_YCbCr_POSITIONING:
if ((pTag->wType == TAG_TYPE_SHORT) && (pTag->dwCount == 1))
{
u16YCbCrPos = pTag->us;
}
break;
case TAG_REF_BLACK_WHITE:
case TAG_THUMBNAIL_REF_BLACK_WHITE:
if ((pTag->wType == TAG_TYPE_RATIONAL) && (pTag->dwCount == 6))
{
int UNALIGNED *piValue = (int UNALIGNED*)(pApp1Data +
pTag->dwOffset);
rYLow = GetValueFromRational(piValue, fBigEndian);
piValue += 2;
rYHigh = GetValueFromRational(piValue, fBigEndian);
piValue += 2;
rCbLow = GetValueFromRational(piValue, fBigEndian);
piValue += 2;
rCbHigh = GetValueFromRational(piValue, fBigEndian);
piValue += 2;
rCrLow = GetValueFromRational(piValue, fBigEndian);
piValue += 2;
rCrHigh = GetValueFromRational(piValue, fBigEndian);
}
break;
default:
break;
}// switch on ID
}// Loop through all the TAGs
// Decode the TIFF image if we have valid bits, width and height. Also, it
// has to be uncompressed TIFF ((u16Compression == 1)
GpMemoryBitmap *pBmp = NULL;
if (pBits && (nWidth != 0) &&(nHeight != 0) && (u16Compression == 1))
{
// Create a GpMemoryBitmap to hold the decoded image
pBmp = new GpMemoryBitmap();
if (pBmp)
{
// Create a memory buffer to hold the result
hr = pBmp->InitNewBitmap(nWidth, nHeight, PIXFMT_24BPP_RGB);
if (SUCCEEDED(hr))
{
// Lock the memory buffer for write
BitmapData bmpData;
hr = pBmp->LockBits(
NULL,
IMGLOCK_WRITE,
PIXFMT_24BPP_RGB,
&bmpData
);
if (SUCCEEDED(hr))
{
// Get the pointer to the memory buffer so that we can write
// to it
BYTE *pBuf = (BYTE*)bmpData.Scan0;
if (u16PhtoInterpo == 2)
{
// Uncompressed RGB TIFF. This is the simplest case,
// just copy the bits from the source
// Before that, we need to be sure we do memory copy
// within the buffer range
int nBufSize = nWidth * nHeight * 3;
if ((pBits + nBufSize) <= (nApp1Length + pApp1Data))
{
// Convert from BGR to RGB
BYTE *pSrc = pBits;
BYTE *pDest = pBuf;
for (int i = 0; i < (int)nHeight; ++i)
{
for (int j = 0; j < (int)nWidth; ++j)
{
pDest[2] = pSrc[0];
pDest[1] = pSrc[1];
pDest[0] = pSrc[2];
pSrc += 3;
pDest += 3;
}
}
}
else
{
WARNING(("DecodeTiffThumb---Not enough src data"));
hr = E_INVALIDARG;
}
}// RGB TIFF
else if ((u16PhtoInterpo == 6) && (u16PlanarConfig == 1))
{
// YCbCr TIFF thumbnail. Data are stored in chunky
// (interleaving) mode
int nMemSize = nWidth * nHeight;
BYTE *pbY = (BYTE*)GpMalloc(nMemSize);
int *pnCb = (int*)GpMalloc(nMemSize * sizeof(int));
int *pnCr = (int*)GpMalloc(nMemSize * sizeof(int));
if (pbY && pnCb && pnCr)
{
if (((2 == u16SubHoriz) && (1 == u16SubVert)) ||
((1 == u16SubHoriz) && (2 == u16SubVert)))
{
// YCbCr 4:2:0 and YCbCr 4:0:2
hr = Get420YCbCrChannels(
nWidth,
nHeight,
pBits,
pbY,
pnCb,
pnCr,
rYLow,
rYHigh,
rCbLow,
rCbHigh,
rCrLow,
rCrHigh
);
}
else if ((2 == u16SubHoriz) && (2 == u16SubVert))
{
// YCbCr 4:2:2
hr = Get422YCbCrChannels(
nWidth,
nHeight,
pBits,
pbY,
pnCb,
pnCr,
rYLow,
rYHigh,
rCbLow,
rCbHigh,
rCrLow,
rCrHigh
);
}
if (SUCCEEDED(hr))
{
if ((0 != rLumRed) && (0 != rLumGreen) &&
(0 != rLumBlue))
{
hr = YCbCrToRgbWithCoeff(
pbY, // Pointer to Y data
pnCb, // Pointer to Cb data
pnCr, // Pointer to Cr data
rLumRed, // Red coefficient
rLumGreen, // Green coefficient
rLumBlue, // Blue coefficient
pBuf, // Pointer to output buffer
nHeight, // Number of rows
nWidth, // Output width
nWidth * 3 // Stride of output buffer
);
}
else
{
hr = YCbCrToRgbNoCoeff(
pbY, // Pointer to Y data
pnCb, // Pointer to Cb data
pnCr, // Pointer to Cr data
pBuf, // Pointer to output buffer
nHeight, // Number of rows
nWidth, // Output width
nWidth * 3 // Stride of output buffer
);
}
}
}// if (pbY && pnCb && pnCr)
else
{
WARNING(("DecodeTiffThumbnail---Out of memory"));
hr = E_OUTOFMEMORY;
}
if (pbY)
{
GpFree(pbY);
}
if (pnCb)
{
GpFree(pnCb);
}
if (pnCr)
{
GpFree(pnCr);
}
}// YCbCr TIFF case
else
{
// Invalid thumbnail format
WARNING(("DecodeTiffThumb--Invalid thumbnail format"));
hr = E_FAIL;
}
if (SUCCEEDED(hr))
{
// Unlock the bits
hr = pBmp->UnlockBits(&bmpData);
if (SUCCEEDED(hr))
{
// Give the thumbnail to caller
*thumbImage = pBmp;
}
}
}// LockBits() succeed
}// InitNewBitmap() succeed
}// If (pBmp)
else
{
WARNING(("DecodeTiffThumbnail--New GpMemoryBitmap() failed"));
hr = E_OUTOFMEMORY;
}
}// If we have a valid thumbnail
// If this function succeed, then we pass the Thumbnail image (a
// GpMemoryBitmap object) to the caller in the "thumbImage" object.
// Otherwise, we have to free it.
if (FAILED(hr))
{
delete pBmp;
}
return hr;
}
static CLSID InternalJpegClsID =
{
0x557cf401,
0x1a04,
0x11d3,
{0x9a, 0x73, 0x00, 0x00, 0xf8, 0x1e, 0xf3, 0x2e}
};
/**************************************************************************\
*
* Function Description:
*
* This function converts a TIFF thumbnail to a JPEG compressed thumbnail.
*
* Return Value:
*
* Status code.
*
\**************************************************************************/
HRESULT
ConvertTiffThumbnailToJPEG(
IN LPBYTE lpApp1Data, // Base address for APP1 chunk
IN LPBYTE lpIFD1, // Base address for IFD 1
IN BOOL fBigEndian, // Flag for endian info
IN INT nApp1Length, // Length of APP1 chunk
IN OUT InternalPropertyItem *pTail, // Tail of the property link list
IN OUT UINT *puiNumOfItems, // Number of property items in the link
// list
IN OUT UINT *puiListSize // Total length of the property value
// buffer
)
{
IImage *pThumbImg = NULL;
// First, get the TIFF thumbnail
HRESULT hr = DecodeTiffThumbnail(
lpApp1Data, // Base address for APP1 chunk
lpIFD1, // Base address for IFD 1
fBigEndian, // Flag for endian info
nApp1Length, // Length of APP1 chunk
&pThumbImg // Result thumbnail will be in IImage format
);
if (SUCCEEDED(hr))
{
// Create a memory stream for writing the JPEG
GpWriteOnlyMemoryStream *pDestStream = new GpWriteOnlyMemoryStream();
if (pDestStream)
{
// Set the buffer size (allocate the memory) based on the size of
// APP1 chunk for holding the result JPEG file.
// Note: the size here is not important since
// GpWriteOnlyMemoryStream() will do realloc if necessary. Here we
// think set the initial buffer of the size of APP1 header should be
// sufficient. The reason is that the APP1 header contains original
// uncompressed TIFF file, plus other information in the APP1
// header. This should be bigger than the result JPEG compressed
// thumbnail.
hr = pDestStream->InitBuffer(nApp1Length);
if (SUCCEEDED(hr))
{
// Since we don't want APP0 in the final JPEG file. Make up a
// encoder parameter to suppress APP0
BOOL fSuppressAPP0 = TRUE;
EncoderParameters encoderParams;
encoderParams.Count = 1;
encoderParams.Parameter[0].Guid = ENCODER_SUPPRESSAPP0;
encoderParams.Parameter[0].Type = TAG_TYPE_BYTE;
encoderParams.Parameter[0].NumberOfValues = 1;
encoderParams.Parameter[0].Value = (VOID*)&fSuppressAPP0;
IImageEncoder *pDstJpegEncoder = NULL;
// Save thumbnail to the memory stream
// Note: this casting might looks dangerous. But it is not since
// we "know" the real thumbnail data from the decoder is in a
// GpMemoryBitmap format.
hr = ((GpMemoryBitmap*)pThumbImg)->SaveToStream(
pDestStream, // Dest stream
&InternalJpegClsID, // JPEG clsID
&encoderParams, // Encoder parameters
FALSE, // Not a special JPEG
&pDstJpegEncoder, // Encoder pointer
NULL // No decoder source
);
if (SUCCEEDED(hr))
{
// Release the encoder object
pDstJpegEncoder->TerminateEncoder();
pDstJpegEncoder->Release();
// Get the bits from the stream and set the property
BYTE *pRawBits = NULL;
UINT nLength = 0;
hr = pDestStream->GetBitsPtr(&pRawBits, &nLength);
if (SUCCEEDED(hr))
{
// We are sure we have a thumbnail image, add it to the
// property list
hr = AddPropertyList(
pTail,
TAG_THUMBNAIL_DATA,
nLength,
TAG_TYPE_BYTE,
pRawBits
);
if (SUCCEEDED(hr))
{
*puiNumOfItems += 1;
*puiListSize += nLength;
}
}
}// SaveToStream succeed
}// InitBuffer() succeed
pDestStream->Release();
}// Create GpWriteOnlyMemoryStream() succeed
// Release the source thumbnail image
pThumbImg->Release();
}
return hr;
}
/**************************************************************************\
*
* Function Description:
*
* This function converts a GDI+ internal thumbnail TAG ID to thumbnail tag
* ID.
*
* Note: the reason we have to do thumbnail ID to GDI+ internal thumbnail tag ID
* during decoding and restore it when writing out is because the original
* thumbnail IDs are the same as the main image IDs, like ImageWidth, Height etc.
* This causes confusion to the final users who use/set/remove TAG ids. That's
* the reason we have to distinguish them.
*
* Return Value:
*
* Status code.
*
\**************************************************************************/
void
ThumbTagToMainImgTag(
PropertyItem *pTag // Property TAG whos ID needs to be converted
)
{
switch (pTag->id)
{
case TAG_THUMBNAIL_COMPRESSION:
pTag->id = TAG_COMPRESSION;
if (*((UINT16 UNALIGNED*)pTag->value) == 1)
{
// GDI+ only writes JPEG compressed thumbnail. So the value should
// be 6
*((UINT16 UNALIGNED*)pTag->value) = 6;
}
break;
case TAG_THUMBNAIL_IMAGE_DESCRIPTION:
pTag->id = TAG_IMAGE_DESCRIPTION;
break;
case TAG_THUMBNAIL_EQUIP_MAKE:
pTag->id = TAG_EQUIP_MAKE;
break;
case TAG_THUMBNAIL_EQUIP_MODEL:
pTag->id = TAG_EQUIP_MODEL;
break;
case TAG_THUMBNAIL_ORIENTATION:
pTag->id = TAG_ORIENTATION;
break;
case TAG_THUMBNAIL_RESOLUTION_X:
pTag->id = TAG_X_RESOLUTION;
break;
case TAG_THUMBNAIL_RESOLUTION_Y:
pTag->id = TAG_Y_RESOLUTION;
break;
case TAG_THUMBNAIL_RESOLUTION_UNIT:
pTag->id = TAG_RESOLUTION_UNIT;
break;
case TAG_THUMBNAIL_TRANSFER_FUNCTION:
pTag->id = TAG_TRANSFER_FUNCTION;
break;
case TAG_THUMBNAIL_SOFTWARE_USED:
pTag->id = TAG_SOFTWARE_USED;
break;
case TAG_THUMBNAIL_DATE_TIME:
pTag->id = TAG_DATE_TIME;
break;
case TAG_THUMBNAIL_ARTIST:
pTag->id = TAG_ARTIST;
break;
case TAG_THUMBNAIL_WHITE_POINT:
pTag->id = TAG_WHITE_POINT;
break;
case TAG_THUMBNAIL_PRIMAY_CHROMATICS:
pTag->id = TAG_PRIMAY_CHROMATICS;
break;
case TAG_THUMBNAIL_YCbCr_COEFFICIENTS:
pTag->id = TAG_YCbCr_COEFFICIENTS;
break;
case TAG_THUMBNAIL_YCbCr_SUBSAMPLING:
pTag->id = TAG_YCbCr_SUBSAMPLING;
break;
case TAG_THUMBNAIL_YCbCr_POSITIONING:
pTag->id = TAG_YCbCr_POSITIONING;
break;
case TAG_THUMBNAIL_REF_BLACK_WHITE:
pTag->id = TAG_REF_BLACK_WHITE;
break;
case TAG_THUMBNAIL_COPYRIGHT:
pTag->id = TAG_COPYRIGHT;
break;
default:
// None thumbnail TAG, do nothing
break;
}
}
/**************************************************************************\
*
* Function Description:
*
* This function walks through the InterOperability IFD and put all the IDs it
* find to the property list.
*
* Return Value:
*
* Status code.
*
\**************************************************************************/
HRESULT
BuildInterOpPropertyList(
IN InternalPropertyItem *pTail, // Tail of property list
IN OUT UINT *puiListSize, // Property list size
UINT *puiNumOfItems, // Total number of property items
BYTE *lpBase, // Base address of
INT count,
IFD_TAG UNALIGNED *pTag,
BOOL bBigEndian
)
{
HRESULT hr = S_OK;
UINT uiListSize = *puiListSize;
UINT uiNumOfItems = *puiNumOfItems;
if ((pTag->wType != TAG_TYPE_LONG) || (pTag->dwCount != 1))
{
WARNING(("BuildInterOpPropertyList: Malformed InterOP ptr"));
return E_FAIL;
}
// Get pointer to InterOP IFD info
BYTE *lpInterOP = lpBase + pTag->dwOffset;
// Figure out how many entries there are, and skip to the data section...
if ((INT)((INT_PTR)lpInterOP + sizeof(WORD) - (INT_PTR)lpBase) > count)
{
WARNING(("BuildInterOpPropertyList---Buffer too small"));
return E_FAIL;
}
WORD wNumEntries = *(WORD UNALIGNED*)lpInterOP;
lpInterOP += sizeof(WORD);
if (bBigEndian)
{
wNumEntries = SWAP_WORD(wNumEntries);
}
if ((INT)((INT_PTR)lpInterOP + sizeof(IFD_TAG) * wNumEntries
-(INT_PTR)lpBase) > count)
{
WARNING(("BuildInterOpPropertyList---Buffer too small"));
return E_FAIL;
}
IFD_TAG UNALIGNED *pInterOPTag = (IFD_TAG UNALIGNED*)lpInterOP;
UINT valueLength;
for (INT i = 0; i < wNumEntries; ++i)
{
IFD_TAG tNewTag;
pInterOPTag = ((IFD_TAG UNALIGNED*)lpInterOP) + i;
if (bBigEndian == TRUE)
{
tNewTag = SwapIFD_TAG(pInterOPTag);
pInterOPTag = &tNewTag;
// Hack here:
if (pInterOPTag->wType == TAG_TYPE_ASCII)
{
pInterOPTag->dwOffset = SWAP_DWORD(pInterOPTag->dwOffset);
}
}
// Change InterOP tags to a identifiable TAGs.
InterOPTagToGpTag(pInterOPTag);
// No need to parse these tags. But we can't add any unknown type
// into the list because we don't know its length
if (pInterOPTag->wType != TAG_TYPE_UNDEFINED)
{
uiNumOfItems++;
hr = AddPropertyListDirect(pTail, lpBase, pInterOPTag,
bBigEndian, &uiListSize);
}
else if (pInterOPTag->dwCount <= 4)
{
// According to the spec, an "UNDEFINED" value is an 8-bits type
// that can take any value depends on the field.
// In case where the value fits in 4 bytes, the value itself is
// recorded. That is, "dwOffset" is the value for these "dwCount"
// fields.
uiNumOfItems++;
uiListSize += pInterOPTag->dwCount;
LPSTR pVal = (LPSTR)&pInterOPTag->dwOffset;
if (bBigEndian)
{
char cTemp0 = pVal[0];
char cTemp1 = pVal[1];
pVal[0] = pVal[3];
pVal[1] = pVal[2];
pVal[2] = cTemp1;
pVal[3] = cTemp0;
}
hr = AddPropertyList(
pTail,
pInterOPTag->wTag,
pInterOPTag->dwCount,
pInterOPTag->wType,
pVal
);
}// ( pInterOPTag->dwCount <= 4 )
else
{
uiNumOfItems++;
uiListSize += pInterOPTag->dwCount;
PVOID pTemp = lpBase + pInterOPTag->dwOffset;
hr = AddPropertyList(
pTail,
pInterOPTag->wTag,
pInterOPTag->dwCount,
TAG_TYPE_UNDEFINED,
pTemp
);
}// ( pInterOPTag->dwCount > 4 )
if (FAILED(hr))
{
WARNING(("BuildInterOpPropertyList---AddPropertyList failed"));
return hr;
}
}// Loop through all the INTEROP IFD entries
*puiListSize = uiListSize;
*puiNumOfItems = uiNumOfItems;
return hr;
}
/**************************************************************************\
*
* Function Description:
*
* This function converts the InterOperability TAG ID to GDI+ internal
* InterOperability tag ID.
*
* Note: the reason we have to do InterOperability ID to GDI+ internal tag ID
* during decoding and restore it when writing out is because the original
* InterOperability ID is only 1,2,3,4... This conflicts with tags under GPS
* section.
*
* Return Value:
*
* Status code.
*
\**************************************************************************/
void
InterOPTagToGpTag(
IFD_TAG UNALIGNED *pInterOPTag
)
{
switch (pInterOPTag->wTag)
{
case 1:
pInterOPTag->wTag = TAG_INTEROP_INDEX;
break;
case 2:
pInterOPTag->wTag = TAG_INTEROP_EXIFR98VER;
break;
default:
break;
}
return;
}
/**************************************************************************\
*
* Function Description:
*
* This function restores the proper InterOperability TAG ID from GDI+
* internal InterOperability ID.
*
* Note: the reason we have to do InterOperability ID to GDI+ internal tag ID
* during decoding and restore it when writing out is because the original
* InterOperability ID is only 1,2,3,4... This conflicts with tags under GPS
* section.
*
* Return Value:
*
* Status code.
*
\**************************************************************************/
void
RestoreInterOPTag(
IFD_TAG UNALIGNED *pInterOPTag // Pointer to TAG to be changed
)
{
switch (pInterOPTag->wTag)
{
case TAG_INTEROP_INDEX:
pInterOPTag->wTag = 1;
break;
case TAG_INTEROP_EXIFR98VER:
pInterOPTag->wTag = 2;
break;
default:
break;
}
return;
}
/**************************************************************************\
*
* Function Description:
*
* This function returns TRUE if a given property ID belongs to
* InterOperability IFD. Otherwise return FALSE.
*
* Return Value:
*
* Status code.
*
\**************************************************************************/
BOOL
IsInInterOPIFDSection(
PROPID id // ID of TAG to be verified
)
{
switch (id)
{
case TAG_INTEROP_INDEX:
case TAG_INTEROP_EXIFR98VER:
return TRUE;
default:
return FALSE;
}
}
/**************************************************************************\
*
* Function Description:
*
* This function writes an InterOperability IFD tags inside Exif IFD
*
* Return Value:
*
* Status code.
*
\**************************************************************************/
HRESULT
WriteInterOPIFD(
IN OUT BYTE *pbMarkerBuffer, // Points to the beginning of APP1 buffer
IN PropertyItem *pPropertyList, // The list of input property
IN UINT cPropertyItems, // Number of property items in the list
IN UINT cInterOPTags, // Number of InterOperability tags
IN OUT UINT *puiTotalBytesWritten // Counter for total bytes written so far
)
{
HRESULT hr = S_OK;
// Fill in the number of entry field, 2 bytes
UINT16 UNALIGNED *pui16NumEntry = (UINT16 UNALIGNED*)(pbMarkerBuffer +
*puiTotalBytesWritten);
*pui16NumEntry = (UINT16)cInterOPTags;
*puiTotalBytesWritten += 2;
UINT uiInterOPSectionLength = sizeof(IFD_TAG) * cInterOPTags;
IFD_TAG *pTagBuf = (IFD_TAG*)GpMalloc(uiInterOPSectionLength);
if (pTagBuf == NULL)
{
WARNING(("EXIF: WriteInterOPIFD failed---Out of memory"));
return E_OUTOFMEMORY;
}
// Remember where to write InterOP IFD, (pbInterOPIFDOffset). We can't write
// all the TAGs now since we can't fill in all the values at this moment
BYTE *pbInterOPIFDOffset = pbMarkerBuffer + (*puiTotalBytesWritten);
// We need to count "uiInterOPSectionLength" bytes as written. This
// makes it easier for counting the offset below.
// Here "+4" is for 4 bytes for writing next IFD offset.
*puiTotalBytesWritten += (uiInterOPSectionLength + 4);
MakeOffsetEven(*puiTotalBytesWritten);
// Figure out the offset for InterOP IFD value section
BYTE *pbCurrent = pbMarkerBuffer + (*puiTotalBytesWritten);
PropertyItem *pItem = pPropertyList;
IFD_TAG *pCurrentTag = NULL;
UINT cInterOPTagsWritten = 0;// Num of InterOP tags have been written so far
for (int i = 0; i < (INT)cPropertyItems; ++i)
{
// Only write InterOP specific TAGs
if (IsInInterOPIFDSection(pItem->id) == TRUE)
{
// Fill out a new TAG structure
pCurrentTag = pTagBuf + cInterOPTagsWritten;
WriteATag(
pbMarkerBuffer,
pCurrentTag,
pItem,
&pbCurrent,
puiTotalBytesWritten
);
RestoreInterOPTag(pCurrentTag);
cInterOPTagsWritten++;
}
// Move onto next PropertyItem
pItem++;
}// Loop through all the property items to write EXIF tags
// After the above loop, we have fill all the fields in all the InterOP TAG
// structure. Write out all the directory entries now
GpMemcpy(pbInterOPIFDOffset, (BYTE*)pTagBuf, uiInterOPSectionLength);
// Add a NULL at the end to terminate the InterOP offset.
*((UINT32 UNALIGNED*)(pbInterOPIFDOffset + uiInterOPSectionLength)) = NULL;
GpFree(pTagBuf);
return hr;
}
//
// Convert some rows of samples to the output colorspace.
//
// Note that we change from noninterleaved, one-plane-per-component format
// to interleaved-pixel format. The output buffer is therefore three times
// as wide as the input buffer.
// A starting row offset is provided only for the input buffer. The caller
// can easily adjust the passed output_buf value to accommodate any row
// offset required on that side.
//
const int c_ScaleBits = 16;
#define ONE_HALF ((INT32) 1 << (c_ScaleBits - 1))
#define FIX(x) ((INT32) ((x) * (1L<<c_ScaleBits) + 0.5))
#define SHIFT_TEMPS INT32 shift_temp;
#define RIGHT_SHIFT(x,shft) \
((shift_temp = (x)) < 0 ? \
(shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \
(shift_temp >> (shft)))
#define RGB_RED 2 /* Offset of Red in an RGB scanline element */
#define RGB_GREEN 1 /* Offset of Green */
#define RGB_BLUE 0 /* Offset of Blue */
/**************************************************************************\
*
* Function Description:
*
* Given Y, Cb, Cr bits stream, this function converts a YCbCr image into RGB
* image.
*
* The conversion equations to be implemented are therefore
* R = Y + 1.40200 * Cr
* G = Y - 0.34414 * Cb - 0.71414 * Cr
* B = Y + 1.77200 * Cb
*
* Return Value:
*
* Status code.
*
\**************************************************************************/
HRESULT
YCbCrToRgbNoCoeff(
IN BYTE *pbY, // Pointer to Y data
IN int *pnCb, // Pointer to Cb data
IN int *pnCr, // Pointer to Cr data
OUT BYTE *pbDestBuf, // Pointer to output buffer
IN int nRows, // Number of rows
IN int nCols, // Number of columns
IN INT nOutputStride // Stride of output buffer
)
{
HRESULT hr = S_OK;
if (pbY && pnCb && pnCr && pbDestBuf && (nRows > 0) && (nCols > 0))
{
int *pnCrRTable = (int*)GpMalloc(256 * sizeof(int));
int *pnCbBTable = (int*)GpMalloc(256 * sizeof(int));
INT32 *pnCrGTable = (INT32*)GpMalloc(256 * sizeof(INT32));
INT32 *pnCbGTable = (INT32*)GpMalloc(256 * sizeof(INT32));
SHIFT_TEMPS
if (pnCrRTable && pnCbBTable && pnCrGTable && pnCbGTable)
{
INT32 x = -128;
// Build the YCbCr to RGB convert table
for (int i = 0; i <= 255; i++)
{
// "i" is the actual input pixel value, in the range [0, 255]
// The Cb or Cr value we are thinking of is x = i - 128
// Cr=>R value is nearest int to 1.40200 * x
pnCrRTable[i] = (int)RIGHT_SHIFT(FIX(1.40200) * x + ONE_HALF,
c_ScaleBits);
// Cb=>B value is nearest int to 1.77200 * x
pnCbBTable[i] = (int)RIGHT_SHIFT(FIX(1.77200) * x + ONE_HALF,
c_ScaleBits);
// Cr=>G value is scaled-up -0.71414 * x
pnCrGTable[i] = (- FIX(0.71414)) * x;
// Cb=>G value is scaled-up -0.34414 * x
// We also add in ONE_HALF so that need not do it in inner loop
pnCbGTable[i] = (- FIX(0.34414)) * x + ONE_HALF;
x++;
}
}
else
{
WARNING(("YCbCrToRgbNoCoeff---Out of memory"));
hr = E_OUTOFMEMORY;
}
if (SUCCEEDED(hr))
{
// YCbCr to RGB Color mapping
BYTE *pbOutputRow = pbDestBuf;
for (int i = 0; i < nRows; ++i)
{
BYTE *pbOutput = pbOutputRow;
for (int j = 0; j < nCols; ++j)
{
int nY = (int)(*pbY++);
int nCb = *pnCb++;
int nCr = *pnCr++;
pbOutput[RGB_RED] = ByteSaturate(nY + pnCrRTable[nCr]);
pbOutput[RGB_GREEN] = ByteSaturate(nY +
((int)RIGHT_SHIFT(pnCbGTable[nCb] + pnCrGTable[nCr],
c_ScaleBits)));
pbOutput[RGB_BLUE] = ByteSaturate(nY + pnCbBTable[nCb]);
pbOutput += 3; // Move onto next pixel.
}
pbOutputRow += nOutputStride;
}
}
if (pnCrRTable)
{
GpFree(pnCrRTable);
}
if (pnCbBTable)
{
GpFree(pnCbBTable);
}
if (pnCrGTable)
{
GpFree(pnCrGTable);
}
if (pnCbGTable)
{
GpFree(pnCbGTable);
}
}
else
{
WARNING(("YCbCrToRgbNoCoeff---Invalid input parameters"));
hr = E_INVALIDARG;
}
return hr;
}
/**************************************************************************\
*
* Function Description:
*
* Given Y, Cb, Cr bits stream and YCbCr coefficients, this function converts
* a YCbCr image into RGB image.
*
* Formula used in this function is from CCIR Recommendation 601-1, "Encoding
* Parameters of Digital Television for Studios".
*
* R = Cr * (2 - 2 * LumaRed) + Y
* G = (Y - LumaBlue * B - LumaRed * R) / LumaGreen
* B = Cb * (2 - 2 * LumaBlue) + Y
*
* Return Value:
*
* Status code.
*
\**************************************************************************/
HRESULT
YCbCrToRgbWithCoeff(
IN BYTE *pbY, // Pointer to Y data
IN int *pnCb, // Pointer to Cb data
IN int *pnCr, // Pointer to Cr data
IN float rLumRed, // Red coefficient
IN float rLumGreen, // Green coefficient
IN float rLumBlue, // Blue coefficient
OUT BYTE *pbDestBuf, // Pointer to output buffer
IN int nRows, // Number of rows
IN int nCols, // Number of columns
IN INT nOutputStride // Stride of output buffer
)
{
HRESULT hr = E_INVALIDARG;
if (pbY && pnCb && pnCr && pbDestBuf && (rLumGreen != 0.0f))
{
BYTE *pbOutputRow = pbDestBuf;
for (int i = 0; i < nRows; ++i)
{
BYTE *pbOutput = pbOutputRow;
for (int j = 0; j < nCols; ++j)
{
int nY = (int)(*pbY++);
int nCb = *pnCb++;
int nCr = *pnCr++;
int nRed = GpRound((float)nY + (float)nCr * 2.0f *
(1.0f - rLumRed));
int nBlue = GpRound((float)nY + (float)nCb * 2.0f *
(1.0f - rLumBlue));
pbOutput[RGB_GREEN] = ByteSaturate(GpRound(((float)nY -
rLumBlue * nBlue -
rLumRed * nRed)/ rLumGreen));
pbOutput[RGB_RED] = ByteSaturate(nRed);
pbOutput[RGB_BLUE] = ByteSaturate(nBlue);
pbOutput += 3; // Move onto next pixel.
}
pbOutputRow += nOutputStride;
}
hr = S_OK;
}
return hr;
}
/**************************************************************************\
*
* Function Description:
*
* Convert the source image bits from YCbCr420 or YCbCr402 format to 3 separate
* channels: Y, Cb, Cr. The reference black and white values for each channel are
* passed in.
*
* The original data is stored as Y00, Y01, Cb0, Cr0, Y02, Y03, Cb1, Cr1......
*
* YCbCr 420 means that image width of the chroma image is half the image width
* of the associated luma image.
*
* YCbCr 402 means that image height of the chroma image is half the image height
* of the associated luma image.
*
* Note: I couldn't find any document regarding how the data is stored for
* YCbCr420 and 402. Exif spec V2.1 has very limited information about YCbCr420.
* It also contains mistakes in the diagram. I have done some reverse engineering
* based on exisitng images from digital cameras and I found that the data for
* these two formats are stored exactly the same. The reason is that the input is
* just a bits stream. It stores 2 Y values then 1 Cb and 1 Cr values, no matter
* chroma width or height is the half of the luma image.
*
* Note: ISSUE-2002/01/30--minliu:
* It will be much faster to build a lookup table based on input Black and
* white values for each channel, instead of calculating scale and offset for
* each pixel. Should be implemented in V2.
*
* Return Value:
*
* Status code.
*
\**************************************************************************/
HRESULT
Get420YCbCrChannels(
IN int nWidth, // Image width
IN int nHeight, // Image height
IN BYTE *pBits, // Poinetr to source data bits in YCbCr format
OUT BYTE *pbY, // Output buffer for Y value
OUT int *pnCb, // Output buffer for Cb value
OUT int *pnCr, // Output buffer for Cr value
IN float rYLow, // Black reference value for Y channel
IN float rYHigh, // White reference value for Y channel
IN float rCbLow, // Black reference value for Cb channel
IN float rCbHigh, // White reference value for Cb channel
IN float rCrLow, // Black reference value for Cr channel
IN float rCrHigh // White reference value for Cr channel
)
{
if ((nWidth <=0) || (nHeight <= 0) || (NULL == pBits) || (NULL == pbY) ||
(NULL == pnCb) || (NULL == pnCr) ||
(rYLow > rYHigh) || (rCbLow > rCbHigh) || (rCrLow > rCrHigh))
{
WARNING(("Get420YCbCrChannels---Invalid input parameter"));
return E_INVALIDARG;
}
HRESULT hr = S_OK;
float rYScale = 1.0f;
float rCbScale = 1.0f;
float rCrScale = 1.0f;
if (rYHigh != rYLow)
{
rYScale = 255.0f / (rYHigh - rYLow);
}
if (rCbHigh != rCbLow)
{
rCbScale = 127.0f / (rCbHigh - rCbLow);
}
if (rCrHigh != rCrLow)
{
rCrScale = 127.0f / (rCrHigh - rCrLow);
}
// Loop through the input data to extract Y, Cb, Cr values.
// ISSUE-2002/01/30--minliu: Read the "Notes" above for future improvement
for (int i = 0; i < nHeight; i++)
{
for (int j = 0; j < nWidth / 2; j++)
{
*pbY++ = ByteSaturate(GpRound((float(*pBits++) - rYLow) * rYScale));
*pbY++ = ByteSaturate(GpRound((float(*pBits++) - rYLow) * rYScale));
// Let two neighboring Cb/Cr has the same value
int nCb = GpRound((float(*pBits++) - rCbLow) * rCbScale);
*pnCb++ = nCb;
*pnCb++ = nCb;
int nCr = GpRound((float(*pBits++) - rCrLow) * rCrScale);
*pnCr++ = nCr;
*pnCr++ = nCr;
}
}
return hr;
}
/**************************************************************************\
*
* Function Description:
*
* Convert the source image bits from YCbCr422 format to 3 separate channels:
* Y, Cb, Cr. The reference black and white values for each channel are passed in
* The original data is stored as Y00, Y01, Y10, Y11, Cb0, Cr0, Y02, Y03, Y12,
* Y13, Cb1, Cr1
*
* YCbCr 422 means that image width and height of the chroma image is half the
* image width and height of the associated luma image.
*
* Note: ISSUE-2002/01/30--minliu:
* It will be much faster to build a lookup table based on input Black and
* white values for each channel, instead of calculating scale and offset for
* each pixel. Should be implemented in V2.
*
* Return Value:
*
* Status code.
*
\**************************************************************************/
HRESULT
Get422YCbCrChannels(
IN int nWidth, // Image width
IN int nHeight, // Image height
IN BYTE *pBits, // Poinetr to source data bits in YCbCr format
OUT BYTE *pbY, // Output buffer for Y value
OUT int *pnCb, // Output buffer for Cb value
OUT int *pnCr, // Output buffer for Cr value
IN float rYLow, // Black reference value for Y channel
IN float rYHigh, // White reference value for Y channel
IN float rCbLow, // Black reference value for Cb channel
IN float rCbHigh, // White reference value for Cb channel
IN float rCrLow, // Black reference value for Cr channel
IN float rCrHigh // White reference value for Cr channel
)
{
if ((nWidth <=0) || (nHeight <= 0) || (NULL == pBits) || (NULL == pbY) ||
(NULL == pnCb) || (NULL == pnCr) ||
(rYLow > rYHigh) || (rCbLow > rCbHigh) || (rCrLow > rCrHigh))
{
WARNING(("Get422YCbCrChannels---Invalid input parameter"));
return E_INVALIDARG;
}
HRESULT hr = S_OK;
float rYScale = 1.0f;
float rCbScale = 1.0f;
float rCrScale = 1.0f;
if (rYHigh != rYLow)
{
rYScale = 255.0f / (rYHigh - rYLow);
}
if (rCbHigh != rCbLow)
{
rCbScale = 127.0f / (rCbHigh - rCbLow);
}
if (rCrHigh != rCrLow)
{
rCrScale = 127.0f / (rCrHigh - rCrLow);
}
BYTE *pbYRow = pbY;
int *pnCbRow = pnCb;
int *pnCrRow = pnCr;
int nGap = 2 * nWidth;
// Loop through the input data to extract Y, Cb, Cr values.
// ISSUE-2002/01/30--minliu: Read the "Notes" above for future improvement
for (int i = 0; i < nHeight / 2; i++)
{
BYTE *pbOddYRow = pbYRow; // Odd row pointer in output buffer
BYTE *pbEvenYRow = pbYRow + nWidth; // Even row pointer in output buffer
int *pnOddCbRow = pnCbRow;
int *pnEvenCbRow = pnCbRow + nWidth;
int *pnOddCrRow = pnCrRow;
int *pnEvenCrRow = pnCrRow + nWidth;
for (int j = 0; j < nWidth / 2; j++)
{
// Read 4 Y values first
*pbOddYRow++ = ByteSaturate(GpRound((float(*pBits++) - rYLow) *
rYScale));
*pbOddYRow++ = ByteSaturate(GpRound((float(*pBits++) - rYLow) *
rYScale));
*pbEvenYRow++ = ByteSaturate(GpRound((float(*pBits++) - rYLow) *
rYScale));
*pbEvenYRow++ = ByteSaturate(GpRound((float(*pBits++) - rYLow) *
rYScale));
// Let two neighboring columns and two neighboring rows Cb/Cr all
// have the same value
int nCb = GpRound((float(*pBits++) - rCbLow) * rCbScale);
*pnOddCbRow++ = nCb;
*pnOddCbRow++ = nCb;
*pnEvenCbRow++ = nCb;
*pnEvenCbRow++ = nCb;
int nCr = GpRound((float(*pBits++) - rCrLow) * rCrScale);
*pnOddCrRow++ = nCr;
*pnOddCrRow++ = nCr;
*pnEvenCrRow++ = nCr;
*pnEvenCrRow++ = nCr;
}
pbYRow += nGap; // Move up two rows
pnCbRow += nGap; // Move up two rows
pnCrRow += nGap; // Move up two rows
}
return hr;
}
/**************************************************************************\
*
* Function Description:
*
* Given an GpMemoryBitmap object (mainly for thumbnail images), this function
* converts it to a memory stream in JPEG format. Then adds it to the property
* list. The size of the JPEG stream is returned to caller through
* "puThumbLength".
*
* Return Value:
*
* Status code.
*
\**************************************************************************/
HRESULT
AddThumbToPropertyList(
IN InternalPropertyItem* pTail, // Tail to property item list
IN GpMemoryBitmap *pThumbImg, // Thumbnail image
IN INT nSize, // Minimum size to hold the dest JPEG image
OUT UINT *puThumbLength // Total bytes of thumbanil data
)
{
if ((NULL == puThumbLength) || (nSize <= 0) || (NULL == pThumbImg) ||
(NULL == pTail))
{
WARNING(("AddThumbToPropertyList---Invalid input parameter"));
return E_INVALIDARG;
}
HRESULT hr = S_OK;
// Create a memory stream for holding the JPEG
GpWriteOnlyMemoryStream *pDestStream = new GpWriteOnlyMemoryStream();
if (pDestStream)
{
// Set initial memory buffer size for the image
// Note: this size might be too small for the final JPEG image. But the
// GpWriteOnlyMemoryStream object will expand the memory buffer when
// needed
hr = pDestStream->InitBuffer((UINT)nSize);
if (SUCCEEDED(hr))
{
// Since we don't want APP0 in the final JPEG file. Make up an
// encoder parameter to suppress APP0
BOOL fSuppressAPP0 = TRUE;
EncoderParameters encoderParams;
encoderParams.Count = 1;
encoderParams.Parameter[0].Guid = ENCODER_SUPPRESSAPP0;
encoderParams.Parameter[0].Type = TAG_TYPE_BYTE;
encoderParams.Parameter[0].NumberOfValues = 1;
encoderParams.Parameter[0].Value = (VOID*)&fSuppressAPP0;
// Save thumbnail to the memory stream
IImageEncoder *pDstJpegEncoder = NULL;
hr = ((GpMemoryBitmap*)pThumbImg)->SaveToStream(
pDestStream, // Dest stream
&InternalJpegClsID, // JPEG clsID
&encoderParams, // Encoder parameters
FALSE, // Not a special JPEG
&pDstJpegEncoder, // Encoder pointer
NULL // No decoder source
);
if (SUCCEEDED(hr))
{
// We have got the JPEG data in pDestStream now. Terminate the
// encoder object
ASSERT(pDstJpegEncoder != NULL);
pDstJpegEncoder->TerminateEncoder();
pDstJpegEncoder->Release();
// Get the bits from the stream and set the property
// Note: GetBitsPtr() just gives us a pointer to the memory
// stream. This function here doesn't own the memory. The memory
// will be released when pDestStream->Release() is called
BYTE *pThumbBits = NULL;
UINT uThumbLength = 0;
hr = pDestStream->GetBitsPtr(&pThumbBits, &uThumbLength);
if (SUCCEEDED(hr))
{
// Add thumbnail data to property list
// Note: AddPropertyList() will make a copy of the data
hr = AddPropertyList(
pTail,
TAG_THUMBNAIL_DATA,
uThumbLength,
TAG_TYPE_BYTE,
(void*)pThumbBits
);
if (SUCCEEDED(hr))
{
// Tell the caller the size of thumbnail data we saved
*puThumbLength = uThumbLength;
}
}
}// SaveToStream succeed
}// InitBuffer() succeed
pDestStream->Release();
}// Create GpWriteOnlyMemoryStream() succeed
else
{
WARNING(("AddThumbToPropertyList--GpWriteOnlyMemoryStream() failed"));
hr = E_OUTOFMEMORY;
}
return hr;
}