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;
}
}