-
Notifications
You must be signed in to change notification settings - Fork 39
Description
Hi,
I came across a very strange issue extracting the colorprofile from a TIFF that has an ExifIFD with FreeImage_GetICCProfile. Randomly the ICC data is ok, and other times it contains garbage in release mode or "0xFE 0xEE" in debug modes. And in other cases it retrieves a correct ICC profile data.
The problem is only for TIFFs and only for those tiffs, that have a ExifIFD. The reson for the problem is in the PluginTIFF.cpp in the loader. The pointer to the iccData is queried relatively early with
TIFFGetField(tif, TIFFTAG_ICCPROFILE, &iccSize, &iccBuf);
at the end of the function the profile is then created for FIBITMAP with
FreeImage_CreateICCProfile(dib, iccBuf, iccSize);
The problem is in the function called prior to the creation of the ICC Profile:
ReadMetadata(io, handle, tif, dib);
ReadMetadata will call the following functions:
-> tiff_read_exif_profile
-> TIFFReadEXIFDirectory (in case the image contains an ExifIFD)
-> TIFFReadCustomDirectory
-> TIFFFreeDirectory <--- this will delete the ICC data previously allocated and thereby will invalidate the memory pointed to by iccBuf.
The iccProfile is read again later, and in some rare cases the same memory is used again or depending on the crt settings the data contains special marks.
I wrote a test for the case, in order to detect the "intermediate deletion" of the buffer, I used a special crt debug flag, that will delay reuse of the freed memory blocks.
void testICCProfile(const char* lpszPathName) {
int tmp;
// use the _CRTDBG_DELAY_FREE_MEM_DF flag to delay the freeing of memory
// freed memory will be marked with 0xDD. In this way we can check our
// buffer later whether it was freed or not
tmp = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
_CrtSetDbgFlag(tmp | _CRTDBG_DELAY_FREE_MEM_DF);
// load the image
FIBITMAP* dib = NULL;
FREE_IMAGE_FORMAT fif = FreeImage_GetFileType(lpszPathName);
dib = FreeImage_Load(fif, lpszPathName, 0);
assert(dib != NULL);
// get the ICC profile
const FIICCPROFILE* iccProfile = FreeImage_GetICCProfile(dib);
assert(iccProfile != NULL);
assert(iccProfile->data != NULL);
assert(iccProfile->size > 0);
// dump profile to disk
FILE* f = fopen("icc_profile.icc", "wb");
assert(f != NULL);
fwrite(iccProfile->data, 1, iccProfile->size, f);
fclose(f);
// the first bytes of the profile should not be the dead data marker 0xDD
printf("ICC profile size: %d\n", iccProfile->size);
// print the first 16 bytes of the profile
for (int i = 0; i < 16; i++) {
printf("%02x ", ((unsigned char*)(iccProfile->data))[i]);
}
// check the first bytes of the profile, in case the buffer was freed,
// the data is set to 0xDD, if we see 0xDD, the buffer was freed and a new buffer
// was allocated for the ICC profile. In the good case we should see the image profile
// data here
assert(((unsigned char*)(iccProfile->data))[0] != 0xDD);
assert(((unsigned char*)(iccProfile->data))[1] != 0xDD);
assert(((unsigned char*)(iccProfile->data))[2] != 0xDD);
assert(((unsigned char*)(iccProfile->data))[3] != 0xDD);
// unload the dib
FreeImage_Unload(dib);
// reset the crt debug flag
_CrtSetDbgFlag(tmp);
}
I'm not that deep into the TIFF format and the data structures used in the libTIFF. As a current workaround, I simply reget the iccBuf after reading the metadata:
// copy ICC profile data (must be done after FreeImage_Allocate)
if (iccBuf != nullptr)
{
// reget the iccBuf, since the pointer may have changed in ReadMetadata
TIFFGetField(tif, TIFFTAG_ICCPROFILE, &iccSize, &iccBuf);
FreeImage_CreateICCProfile(dib, iccBuf, iccSize);
...
}
The image I used for testing can be found here: clone_with_profile.zip
I created it with image magick from the clone.tif by adding a standard sRGB color profile:
magick clone.tif -profile srgb.icc clone_with_profile.tif
and then added a creation date with ExifTool
