Hi,

The attached patch and test are an attempt at fixing bug 6523398. I apologize that it is a little hacky, but IMO it's no worse than the rest of LCMS.

I have not even attempted to send the LCMS patches directly upstream. I do not believe that LCMS was ever designed to do anything other than simply read or write profiles (and do the defined colorspace transformations and whatnot).

Keith
*** openjdk/jdk/src/share/native/sun/java2d/cmm/lcms/lcms.h.orig	2008-04-11 19:17:54.000000000 -0700
--- openjdk/jdk/src/share/native/sun/java2d/cmm/lcms/lcms.h	2008-04-11 19:19:48.000000000 -0700
*************** LCMSAPI BOOL LCMSEXPORT _cmsSaveProfile(
*** 1244,1250 ****
  LCMSAPI BOOL LCMSEXPORT _cmsSaveProfileToMem(cmsHPROFILE hProfile, void *MemPtr,
                                                                  size_t* BytesNeeded);
  
! 
  
  // PostScript ColorRenderingDictionary and ColorSpaceArray
  
--- 1244,1251 ----
  LCMSAPI BOOL LCMSEXPORT _cmsSaveProfileToMem(cmsHPROFILE hProfile, void *MemPtr,
                                                                  size_t* BytesNeeded);
  
! // Modify data for a tag in a profile
! LCMSAPI BOOL LCMSEXPORT _cmsModifyTagData(cmsHPROFILE hProfile, icTagSignature sig, void *data, size_t size);
  
  // PostScript ColorRenderingDictionary and ColorSpaceArray
  
*************** typedef struct _lcms_iccprofile_struct {
*** 1838,1843 ****
--- 1839,1845 ----
                 BOOL   (* Seek)(struct _lcms_iccprofile_struct* Icc, size_t offset);
                 BOOL   (* Close)(struct _lcms_iccprofile_struct* Icc);
                 size_t (* Tell)(struct _lcms_iccprofile_struct* Icc);
+                BOOL   (* Grow)(struct _lcms_iccprofile_struct* Icc, size_t amount);
  
                 // Writting
  
*** openjdk/jdk/src/share/native/sun/java2d/cmm/lcms/LCMS.c.orig	2008-04-11 19:18:43.000000000 -0700
--- openjdk/jdk/src/share/native/sun/java2d/cmm/lcms/LCMS.c	2008-04-11 19:19:48.000000000 -0700
*************** JNIEXPORT void JNICALL Java_sun_java2d_c
*** 347,353 ****
  JNIEXPORT void JNICALL Java_sun_java2d_cmm_lcms_LCMS_setTagData
    (JNIEnv *env, jobject obj, jlong id, jint tagSig, jbyteArray data)
  {
!     fprintf(stderr, "setTagData operation is not implemented");
  }
  
  void* getILData (JNIEnv *env, jobject img, jint* pDataType,
--- 347,368 ----
  JNIEXPORT void JNICALL Java_sun_java2d_cmm_lcms_LCMS_setTagData
    (JNIEnv *env, jobject obj, jlong id, jint tagSig, jbyteArray data)
  {
!     cmsHPROFILE profile;
!     storeID_t sProf;
!     jbyte* dataArray;
!     int tagSize;
! 
!     if (tagSig == SigHead) {
!       fprintf(stderr, "setTagData on icSigHead not permitted");
!       return;
!     }
! 
!     sProf.j = id;
!     profile = (cmsHPROFILE) sProf.pf;
!     dataArray = (*env)->GetByteArrayElements(env, data, 0);    
!     tagSize =(*env)->GetArrayLength(env, data);
!     _cmsModifyTagData(profile, (icTagSignature) tagSig, dataArray, tagSize);
!     (*env)->ReleaseByteArrayElements(env, data, dataArray, 0);
  }
  
  void* getILData (JNIEnv *env, jobject img, jint* pDataType,
*** openjdk/jdk/src/share/native/sun/java2d/cmm/lcms/cmsio0.c.orig	2008-04-11 19:17:54.000000000 -0700
--- openjdk/jdk/src/share/native/sun/java2d/cmm/lcms/cmsio0.c	2008-04-11 19:19:48.000000000 -0700
*************** BOOL MemoryWrite(struct _lcms_iccprofile
*** 157,164 ****
         if (size == 0) return TRUE;
  
         if (ResData != NULL)
!            CopyMemory(ResData ->Block + Icc ->UsedSpace, Ptr, size);
  
         Icc->UsedSpace += size;
  
         return TRUE;
--- 157,165 ----
         if (size == 0) return TRUE;
  
         if (ResData != NULL)
!            CopyMemory(ResData ->Block + ResData ->Pointer, Ptr, size);
  
+        ResData->Pointer += size;
         Icc->UsedSpace += size;
  
         return TRUE;
*************** BOOL MemoryWrite(struct _lcms_iccprofile
*** 166,171 ****
--- 167,184 ----
  
  
  static
+ BOOL MemoryGrow(struct _lcms_iccprofile_struct* Icc, size_t size)
+ {
+   FILEMEM* ResData = (FILEMEM*) Icc->stream;
+   ResData->Size += size;
+   ResData->Block = realloc(ResData->Block, ResData->Size);
+   if (!ResData->Block)
+     return FALSE;
+   return TRUE;
+ }
+ 
+ 
+ static
  BOOL MemoryClose(struct _lcms_iccprofile_struct* Icc)
  {
      FILEMEM* ResData = (FILEMEM*) Icc ->stream;
*************** BOOL FileWrite(struct _lcms_iccprofile_s
*** 239,244 ****
--- 252,264 ----
  
  
  static
+ BOOL FileGrow(struct _lcms_iccprofile_struct* Icc, size_t size)
+ {
+   return TRUE;
+ }
+ 
+ 
+ static
  BOOL FileClose(struct _lcms_iccprofile_struct* Icc)
  {
      return fclose((FILE*) Icc ->stream);
*************** LPLCMSICCPROFILE _cmsCreateProfileFromFi
*** 382,387 ****
--- 402,408 ----
      NewIcc ->Seek  = FileSeek;
      NewIcc ->Tell  = FileTell;
      NewIcc ->Close = FileClose;
+     NewIcc ->Grow  = FileGrow;
      NewIcc ->Write = NULL;
  
      NewIcc ->IsWrite = FALSE;
*************** LPLCMSICCPROFILE _cmsCreateProfileFromMe
*** 419,425 ****
      NewIcc ->Seek  = MemorySeek;
      NewIcc ->Tell  = MemoryTell;
      NewIcc ->Close = MemoryClose;
!     NewIcc ->Write = NULL;
  
      NewIcc ->IsWrite = FALSE;
  
--- 440,447 ----
      NewIcc ->Seek  = MemorySeek;
      NewIcc ->Tell  = MemoryTell;
      NewIcc ->Close = MemoryClose;
!     NewIcc ->Grow  = MemoryGrow;
!     NewIcc ->Write = MemoryWrite;
  
      NewIcc ->IsWrite = FALSE;
  
*** openjdk/jdk/src/share/native/sun/java2d/cmm/lcms/cmsio1.c.orig	2008-04-11 19:17:54.000000000 -0700
--- openjdk/jdk/src/share/native/sun/java2d/cmm/lcms/cmsio1.c	2008-04-11 19:19:48.000000000 -0700
*************** CleanUp:
*** 3661,3663 ****
--- 3661,3800 ----
      CopyMemory(Icc, &Keep, sizeof(LCMSICCPROFILE));
      return FALSE;
  }
+ 
+ BOOL LCMSEXPORT _cmsModifyTagData(cmsHPROFILE hProfile, icTagSignature sig,
+ 				  void *data, size_t size)
+ {
+   BOOL isNew;
+   int i, idx, delta, count;
+   LPBYTE padChars[3] = {0, 0, 0};
+   LPBYTE beforeBuf, afterBuf, ptr;
+   size_t beforeSize, afterSize;
+   icUInt32Number profileSize, temp;
+   LPLCMSICCPROFILE Icc = (LPLCMSICCPROFILE) (LPSTR) hProfile;
+ 
+   isNew = FALSE;
+   idx = _cmsSearchTag(Icc, sig, FALSE);
+   if (idx < 0)
+     {
+       isNew = TRUE;
+       idx = Icc->TagCount++;
+       if (Icc->TagCount >= MAX_TABLE_TAG)
+ 	{
+ 	  fprintf(stderr, "Too many tags (%d)\n", Icc->TagCount);
+ 	  Icc->TagCount = MAX_TABLE_TAG-1;
+ 	  return FALSE;
+ 	}
+     }
+ 
+   /* Read in size from header */
+   Icc->Seek(Icc, 0);
+   Icc->Read(&profileSize, sizeof(icUInt32Number), 1, Icc);
+   AdjustEndianess32((LPBYTE) &profileSize);
+ 
+   /* Compute the change in profile size */
+   if (isNew)
+     delta = sizeof(icTag) + ALIGNLONG(size);
+   else
+     delta = ALIGNLONG(size) - ALIGNLONG(Icc->TagSizes[idx]);
+ 
+   /* Add tag to internal structures */
+   ptr = malloc(size);
+   CopyMemory(ptr, data, size);
+   Icc->TagSizes[idx] = size;
+   Icc->TagNames[idx] = sig;
+   if (Icc->TagPtrs[idx])
+     free(Icc->TagPtrs[idx]);
+   Icc->TagPtrs[idx] = ptr;
+   if (isNew)
+     Icc->TagOffsets[idx] = profileSize;
+ 
+   /* Compute size of tag data before/after the modified tag */
+   beforeSize = Icc->TagOffsets[idx] - Icc->TagOffsets[0];
+   if (Icc->TagCount == (idx + 1))
+     afterSize = 0;
+   else
+     afterSize = profileSize - Icc->TagOffsets[idx+1];
+ 
+   /* Make copies of the data before/after the modified tag */
+   if (beforeSize > 0)
+     {
+       beforeBuf = malloc(beforeSize);
+       Icc->Seek(Icc, Icc->TagOffsets[0]);
+       Icc->Read(beforeBuf, beforeSize, 1, Icc);
+     }
+   
+   if (afterSize > 0)
+     {
+       afterBuf = malloc(afterSize);
+       Icc->Seek(Icc, Icc->TagOffsets[idx+1]);
+       Icc->Read(afterBuf, afterSize, 1, Icc);
+     }
+ 
+   /* Update the profile size in the header */
+   profileSize += delta;
+   Icc->Seek(Icc, 0);
+   temp = TransportValue32(profileSize);
+   Icc->Write(Icc, sizeof(icUInt32Number), &temp);
+ 
+   Icc->Grow(Icc, delta);
+ 
+   /* Adjust tag offsets: if the tag is new, we must account
+      for the new tag table entry; otherwise, only those tags after
+      the modified tag are changed (by delta) */
+   if (isNew)
+     {
+       for (i = 0; i < Icc->TagCount; ++i)
+ 	Icc->TagOffsets[i] += sizeof(icTag);
+     }
+   else
+     {
+       for (i = idx+1; i < Icc->TagCount; ++i)
+ 	Icc->TagOffsets[i] += delta;
+     }
+ 
+   /* Write out a new tag table */
+   count = 0;
+   for (i = 0; i < Icc->TagCount; ++i)
+     {
+       if (Icc->TagNames[i] != 0)
+ 	++count;
+     }
+   Icc->Seek(Icc, sizeof(icHeader));
+   temp = TransportValue32(count);
+   Icc->Write(Icc, sizeof(icUInt32Number), &temp);
+ 
+   for (i = 0; i < Icc->TagCount; ++i)
+     {
+       if (Icc->TagNames[i] != 0)
+ 	{
+ 	  icTag tag;
+ 	  tag.sig = TransportValue32(Icc->TagNames[i]);
+ 	  tag.offset = TransportValue32((icInt32Number) Icc->TagOffsets[i]);
+ 	  tag.size = TransportValue32((icInt32Number) Icc->TagSizes[i]);
+ 	  Icc->Write(Icc, sizeof(icTag), &tag);
+ 	}
+     }
+ 
+   /* Write unchanged data before the modified tag */
+   if (beforeSize > 0)
+     {
+       Icc->Write(Icc, beforeSize, beforeBuf);
+       free(beforeBuf);
+     }
+ 
+   /* Write modified tag data */
+   Icc->Write(Icc, size, data);
+   if (size % 4)
+     Icc->Write(Icc, 4 - (size % 4), padChars);
+ 
+   /* Write unchanged data after the modified tag */
+   if (afterSize > 0)
+     {
+       Icc->Write(Icc, afterSize, afterBuf);
+       free(afterBuf);
+     }
+ 
+   return TRUE;
+ }
+ 
/**
 * @test
 * @bug 6523398
 * @summary Verify java2d.LCMS.setTagData
 * @run main SetTagDataTest
 *
 */

import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
import java.nio.ByteBuffer;
import java.util.Arrays;

public class SetTagDataTest {
	// Represents an entry in the tag table
	private static class Tag {
		public int name;
		public int offset;
		public int size;
		
		public Tag(int name, int offset, int size) {
			this.name = name;
			this.offset = offset;
			this.size = size;
		}
		
		public String toString() {
			return "Tag(" + name + "," + offset + "," + size + ")";
		}
	}
	
	// Size of an entry in the tag table
	private static final int TAGTABLE_ENTRY_SIZE = 12;

	// Profile to use
	private static final int PROFILE = ColorSpace.CS_GRAY;
	
	// Any tag not in the above profile
	private static final int NEW_TAG = ICC_Profile.icSigAToB0Tag;
	
	public static void main(String[] args) {
		SetTagDataTest t = new SetTagDataTest();
		t.test_new();
		t.test_existing();
		System.out.println("finished");
	}
	
	// Test setting data for a tag which does not exist in the profile.
	private void test_new() {
		System.out.println("Test: set tag data for new tag");
		
		ICC_Profile orig = ICC_Profile.getInstance(PROFILE);
		
		// Check if the tag we're using exists in the profile (it should not)
		byte[] oldData = orig.getData(NEW_TAG);
		if (oldData != null) {
			// This is a test error. If this occurs, use some other tag!
			throw new RuntimeException("error: profile already contains tag AToB0");
		}
		
		// Save a copy of the original profile
		oldData = orig.getData();
		
		// Test for several different sizes of data
		for (int size = 42; size < 50; ++size) {
			System.out.print("testing data size of " + size + ": ");
			ICC_Profile pf = ICC_Profile.getInstance(oldData);
			byte[] newTagData = new byte[size];
			for (int i = 0; i < size; ++i)
				newTagData[i] = (byte) i;
		
			pf.setData(NEW_TAG, newTagData);
			compare_profiles(orig, pf, NEW_TAG, newTagData, true);
			System.out.println("okay");
		}
	}
	
	private void test_existing() {
		System.out.println("Test: modify existing tag data");
		ICC_Profile orig = ICC_Profile.getInstance(PROFILE);
		byte[] origData = orig.getData();
		
		// Compute max tag element data size
		Tag[] tags = getTags(orig);		
		int i, max = 0;
		for (i = 0; i < tags.length; ++i) {
			if (tags[i].size > max)
				max = tags[i].size;
		}
		
		// Test replacing all tag element data with minimal data
		// We assume that 1 byte is smaller than any of data sizes.
		byte[] newData = { 0x21 };
		for (i = 0; i < tags.length; ++i) {
			System.out.print("replacing " + tags[i].size + " btyes of data "
					+ "at tag index " + i + " with " + newData.length
					+ " bytes: ");

			// Start with a fresh profile
			ICC_Profile pf = ICC_Profile.getInstance(origData);
			pf.setData(tags[i].name, newData);
			compare_profiles(orig, pf, tags[i].name, newData, false);
			System.out.println("okay");
		}
		
		// Test replacing all tag element data with more data
		max += 256;
		newData = new byte[max];
		for (i = 0; i < max; ++i)
			newData[i] = (byte) (i % 16);
		for (i = 0; i < tags.length; ++i) {
			System.out.print("replacing " + tags[i].size + " btyes of data "
					+ "at tag index " + i + " with " + newData.length
					+ " bytes: ");
			
			// Start with a fresh profile
			ICC_Profile pf = ICC_Profile.getInstance(origData);
			pf.setData(tags[i].name, newData);
			compare_profiles(orig, pf, tags[i].name, newData, false);
			System.out.println("okay");
		}
	}
	
	private static int pad(int len) {
		return (len + 3) & ~3;
	}

	private void compare_profiles(ICC_Profile o, ICC_Profile n, int tag, byte[] newTagData, boolean isNew) {
		int i;
		
		// Size difference of profiles due to new data, padding, and 
		// any new tag table entry
		byte[] oldData = o.getData(tag);
		int len = oldData == null ? 0 : oldData.length;
		int delta = pad(newTagData.length) - pad(len);
		if (isNew)
			delta += TAGTABLE_ENTRY_SIZE;
		
		oldData = o.getData();
		ByteBuffer Old = ByteBuffer.wrap(oldData);
		byte[] newData = n.getData();
		ByteBuffer New = ByteBuffer.wrap(newData);
		
		// Compare headers, they should be the same except for size
		int oldSize = Old.getInt();
		int newSize = New.getInt();
		if (newSize != (oldSize + delta)) {
			throw new RuntimeException("incorrect profile header size: expected "
					+ (oldSize + delta) + ", got " + newSize);
		}
		
		// Double-check size: it must be a multiple of 4
		if ((oldSize % 4) != 0)
			throw new RuntimeException("standard profile size is not a multiple of four");
		
		if ((newSize % 4) != 0)
			throw new RuntimeException("new profile size is not a multipl of four");

		// Rest of the headers should be the same
		for (i = 0; i < (128 - 4); ++i) {
			byte a, b;
			a = Old.get();
			b = New.get();
			if (a != b)
				throw new RuntimeException("header data mismatch at index " + i);
		}
		
		/* Now check the tag tables. There are two possibilities:
		 * 
		 * When adding a new tag (newTag == true):
		 * - Tag table length will be different (+1 over old size)
		 * - entries will be identical
		 * - there will be a new entry at the end
		 * 
		 * When modifying an existing tag (newTag == false):
		 * - Tag table length will be the same
		 * - entries before the modified tag will be identical
		 * - entries after the modified tag will have different offsets
		 */
		oldSize = Old.getInt();
		newSize = New.getInt();
		if ((isNew && newSize != (oldSize + 1))
				|| (!isNew && newSize != oldSize)) {
			throw new RuntimeException("incorrect tag table size, expected "
					+ (isNew ? oldSize + 1 : oldSize) + ", got " + newSize);
		}
		
		// Build tag tables for the two profiles
		Tag[] oldTags = new Tag[oldSize];
		Tag[] newTags = new Tag[newSize];
		for (i = 0; i < newSize; ++i) {
			if (i < oldSize)
				oldTags[i] = new Tag(Old.getInt(), Old.getInt(), Old.getInt());
			newTags[i] = new Tag(New.getInt(), New.getInt(), New.getInt());
		}
		
		// Now compare the two tag tables
		boolean seen = false;
		int lastOffset = 0, lastSize = 0;
		for (i = 0; i < oldSize; ++i) {
			Tag a = oldTags[i];
			Tag b = newTags[i];

			if (a.name != b.name) {
				throw new RuntimeException("name mismatch in tag table index "
						+ i);
			}
			
			/*
			 * if newTag, delta = TAGTABLE_ENTRY_SIZE for all i
			 * if !newTag, delta = new.length - old.length + padding for
			 *   all i after name
			 */
			int d = isNew ? TAGTABLE_ENTRY_SIZE : 0;
			if (seen) {
				d = delta;
			}
			if (b.offset != (a.offset + d)) {
				throw new RuntimeException("offset mismatch in tag table index "
						+ i);
			}

			if (!isNew && a.name == tag) {
				seen = true;
			}
			
			if ((b.name == tag) && (b.size != newTagData.length)) {
				throw new RuntimeException("incorrect new data length in tag table");
			} else if ((b.name != tag) && (a.size != b.size)) {
				throw new RuntimeException("size mismatch in tag table index "
						+ i);
			}
			
			lastOffset = b.offset;
			lastSize = b.size;
		}
		
		// Check the new entry
		if (isNew) {
			if (newTags[i].name != tag)
				throw new RuntimeException("incorrect name in new tag table entry");
			
			if (newTags[i].size != newTagData.length)
				throw new RuntimeException("incorrect size in new tag table entry");
			
			int off = pad(lastOffset + lastSize);
			if (newTags[i].offset != off)
				throw new RuntimeException("incorrect offset in new tag table entry");
		}
		
		// Finally, check the data for the individual tags
		for (i = 0; i < oldTags.length; ++i) {
			byte[] a = new byte[pad(oldTags[i].size)];
			Old.get(a);
			byte[] b = new byte[pad(newTags[i].size)];
			New.get(b);

			ByteBuffer A, B;
			B = ByteBuffer.wrap(b);
			
			if (newTags[i].name == tag) {
				// replaced data for this tag -- compare against newTagData
				// but pad newTagData first!
				byte[] padded = Arrays.copyOf(newTagData, pad(newTagData.length));
				A = ByteBuffer.wrap(padded);
			} else {
				A = ByteBuffer.wrap(a);
			}
			
			if (A.compareTo(B) != 0)
				throw new RuntimeException("tag data does not match at index " + i);
			
			// Skip padding
			int padding = pad(newTags[i].size) - pad(newTags[i].size);
			for (int j = 0; j < padding; ++j) {
				New.get();
			}
			padding = pad(oldTags[i].size) - pad(oldTags[i].size);
			for (int j = 0; j < padding; ++j) {
				Old.get();
			}
		}
	}
	
	private Tag[] getTags(ICC_Profile pf) {
		ByteBuffer bb = ByteBuffer.wrap(pf.getData());
		byte[] hdr = new byte[128];
		bb.get(hdr);
		int len = bb.getInt();
		Tag[] tags = new Tag[len];
		for (int i = 0; i < len; ++i)
			tags[i] = new Tag(bb.getInt(), bb.getInt(), bb.getInt());
		
		return tags;
	}	
}

Reply via email to