Skip to content

getting colorprofile from TIFF with ExifData randomly fails #27

@eyec-dirk

Description

@eyec-dirk

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

Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions