Skip to main content

Converting YCCK data to sRGB to create an image in Java

2 replies [Last post]
Anonymous

I have some CMYK JPEGs from a PDF which are actually YCCK encoded. I
have read the raster data out using ImageIO so I can then convert to
sRGB.

I am running these through the standard YCC conversion routines, then
adding a CMYK profile and converting to sRGB. Despite using profiles,
I am not getting an exact match.

What am I doing wrong? Is there a better way?

MArkee

/**
* save raw CMYK data by converting to RGB using algorithm
method -
* pdfsages supplied the C source and I have converted -
* This works very well on most colours but not dark shades
which are
* all rolled into black
*
*/
public static BufferedImage iccConvertCMYKImageToRGB(
byte[] buffer,
int w,
int h,boolean debug) {

DeviceCMYKColorSpace cs=new DeviceCMYKColorSpace();

//get cmyk colorspace using Abobe profile
ColorSpace CMYK=cs.getColorSpace();

BufferedImage image = null;

int pixelCount = w * h*4;

double c,m,y,k;
double Y,Cb,Cr,CENTER;

//turn YCCK in Buffer to CYMK using formula
for (int i = 0; i < pixelCount; i = i + 4) {

Y=(buffer[i] & 255);
Cb = (buffer[i+1] & 255)-128;
Cr = (buffer[i+2] & 255)-128;
//CENTER =(buffer[i+3] & 255);

c =( Y +(1.4020d*Cr));
if(c<0d)
c=0d;
if(c>255d)
c=255d;

m =(Y - (0.3441363d*Cb) - (0.71413636d*Cr));
if(m<0d)
m=0d;
if(m>255d)
m=255d;

y =(Y + (1.7718d*Cb));
if(y<0d)
y=0d;
if(y>255d)
y=255d;

buffer[i]=(byte)(255-c);
buffer[i+1]=(byte)(255-m);
buffer[i+2]=(byte)(255-y);
//buffer[i+3]=(byte)(CENTER); //just passed through

}

/**
* create CMYK image from buffer
*/
int[] bands = { 0, 1, 2, 3 };

WritableRaster raster = Raster.createInterleavedRaster(new
DataBufferByte(buffer,buffer.length), w,h,w * 4,4, bands,null);
ColorModel cmykModel = new ComponentColorModel( CMYK, new
int[] { 8, 8, 8, 8 }, false, false, ColorModel.OPAQUE,
DataBuffer.TYPE_BYTE);
image = new BufferedImage(cmykModel, raster, false, null);

/**
* convert to sRGB filtering data via .icm profile (the one
Adobe has in Acrobat)
* Gives exact match on CMYK to sRGB but not YCCK to sRGB
*/
ICC_Profile rgbProfile =
ICC_Profile.getInstance(ColorSpace.CS_sRGB);
rgbCS = new ICC_ColorSpace(rgbProfile);
rgbModel = new ComponentColorModel(rgbCS, new int[] { 8, 8, 8
}, false, false, ColorModel.OPAQUE, DataBuffer.TYPE_BYTE);

WritableRaster rgbRaster
=rgbModel.createCompatibleWritableRaster(w, h);
CSToRGB = new ColorConvertOp(cs.getColorSpace(), rgbCS,
ColorSpaces.hints);
CSToRGB.filter(image.getRaster(), rgbRaster);

//data now sRGB so create image
image =new BufferedImage(w,h,BufferedImage.TYPE_INT_RGB);
image.setData(rgbRaster);

return image;
}

===========================================================================
To unsubscribe, send email to listserv@java.sun.com and include in the body
of the message "signoff JAVA2D-INTEREST". For general help, send email to
listserv@java.sun.com and include in the body of the message "help".

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Alexey Ushakov

Hello Mark,

Could you please provide more info concerning your problem? It's
difficult to say the exact reason without having complete standalone
testcase with appropriate input data.
The problem that you are describing could be caused by limited precision
of kodak color management library (16 bit fixed point) that we are using
for color conversions. But again I cannot say anything determine without
having complete testcase.

Best Regards,
Alexey

Mark Stephens wrote:
> I have some CMYK JPEGs from a PDF which are actually YCCK encoded. I
> have read the raster data out using ImageIO so I can then convert to
> sRGB.
>
> I am running these through the standard YCC conversion routines, then
> adding a CMYK profile and converting to sRGB. Despite using profiles,
> I am not getting an exact match.
>
> What am I doing wrong? Is there a better way?
>
> MArkee
>
> /**
> * save raw CMYK data by converting to RGB using algorithm
> method -
> * pdfsages supplied the C source and I have converted -
> * This works very well on most colours but not dark shades
> which are
> * all rolled into black
> *
> */
> public static BufferedImage iccConvertCMYKImageToRGB(
> byte[] buffer,
> int w,
> int h,boolean debug) {
>
>
> DeviceCMYKColorSpace cs=new DeviceCMYKColorSpace();
>
> //get cmyk colorspace using Abobe profile
> ColorSpace CMYK=cs.getColorSpace();
>
> BufferedImage image = null;
>
> int pixelCount = w * h*4;
>
> double c,m,y,k;
> double Y,Cb,Cr,CENTER;
>
> //turn YCCK in Buffer to CYMK using formula
> for (int i = 0; i < pixelCount; i = i + 4) {
>
> Y=(buffer[i] & 255);
> Cb = (buffer[i+1] & 255)-128;
> Cr = (buffer[i+2] & 255)-128;
> //CENTER =(buffer[i+3] & 255);
>
> c =( Y +(1.4020d*Cr));
> if(c<0d)
> c=0d;
> if(c>255d)
> c=255d;
>
> m =(Y - (0.3441363d*Cb) - (0.71413636d*Cr));
> if(m<0d)
> m=0d;
> if(m>255d)
> m=255d;
>
> y =(Y + (1.7718d*Cb));
> if(y<0d)
> y=0d;
> if(y>255d)
> y=255d;
>
> buffer[i]=(byte)(255-c);
> buffer[i+1]=(byte)(255-m);
> buffer[i+2]=(byte)(255-y);
> //buffer[i+3]=(byte)(CENTER); //just passed through
>
> }
>
> /**
> * create CMYK image from buffer
> */
> int[] bands = { 0, 1, 2, 3 };
>
> WritableRaster raster = Raster.createInterleavedRaster(new
> DataBufferByte(buffer,buffer.length), w,h,w * 4,4, bands,null);
> ColorModel cmykModel = new ComponentColorModel( CMYK, new
> int[] { 8, 8, 8, 8 }, false, false, ColorModel.OPAQUE,
> DataBuffer.TYPE_BYTE);
> image = new BufferedImage(cmykModel, raster, false, null);
>
> /**
> * convert to sRGB filtering data via .icm profile (the one
> Adobe has in Acrobat)
> * Gives exact match on CMYK to sRGB but not YCCK to sRGB
> */
> ICC_Profile rgbProfile =
> ICC_Profile.getInstance(ColorSpace.CS_sRGB);
> rgbCS = new ICC_ColorSpace(rgbProfile);
> rgbModel = new ComponentColorModel(rgbCS, new int[] { 8, 8, 8
> }, false, false, ColorModel.OPAQUE, DataBuffer.TYPE_BYTE);
>
>
> WritableRaster rgbRaster
> =rgbModel.createCompatibleWritableRaster(w, h);
> CSToRGB = new ColorConvertOp(cs.getColorSpace(), rgbCS,
> ColorSpaces.hints);
> CSToRGB.filter(image.getRaster(), rgbRaster);
>
> //data now sRGB so create image
> image =new BufferedImage(w,h,BufferedImage.TYPE_INT_RGB);
> image.setData(rgbRaster);
>
> return image;
> }
>
> ===========================================================================
> To unsubscribe, send email to listserv@java.sun.com and include in the body
> of the message "signoff JAVA2D-INTEREST". For general help, send email to
> listserv@java.sun.com and include in the body of the message "help".
>

===========================================================================
To unsubscribe, send email to listserv@java.sun.com and include in the body
of the message "signoff JAVA2D-INTEREST". For general help, send email to
listserv@java.sun.com and include in the body of the message "help".

mark.stephens@ukonline.co.uk

In message <4727A4FF.9070109@sun.com>
Alexey Ushakov wrote:

> Hello Mark,

> Could you please provide more info concerning your problem? It's
> difficult to say the exact reason without having complete standalone
> testcase with appropriate input data.
> The problem that you are describing could be caused by limited precision
> of kodak color management library (16 bit fixed point) that we are using
> for color conversions. But again I cannot say anything determine without
> having complete testcase.

> Best Regards,
> Alexey

I have found a solution for my problem so posting it here in case its
useful in the future to someone.

There are some JPEGs which describe themselves as CMYK when they are
in fact encoded using a different colorspace called YCCK. The way to
process them is to convert the YCC part to CMY then add back the K and
convert CMYK to sRGB.

If you want to do this accurately you need profiles for both parts -
there are mathematical formulae but they do not get perfect colour
matches.

There are some YCC/CMYK profiles to download as part of the JAICMM
package.

MArk

===========================================================

Code from GPL JPedal PDF library to convert YCCK to sRGB with
profiles.

/**
* save raw CMYK data by converting to RGB using algorithm
method -
* pdfsages supplied the C source and I have converted -
* This works very well on most colours but not dark shades
which are
* all rolled into black
*
* profile is a set of comma deliminated values providing YCC,
CMY and optionally CMYK from
* -Dorg.jpedal.useICC (remember double quotes if it includes
spaces)
* example -Dorg.jpedal.useICC="YCC=C:/profiles/ycc.pf,CMY=C:/
profiles/cmy.pf
* (can include optional CMYK= values if you wish to change
default CMYK profile used)
*
*
*/
private static ICC_ColorSpace YCC=null,CMY=null;
private static ColorSpace CMYK=null;

public static BufferedImage iccConvertCMYKImageToRGB(
byte[] buffer,
int w,
int h,String profile) throws IOException {

/**
* get profile values for colorspaces for ICC colorspaces
*/
String YCCprofile=null,CMYprofile=null,CMYKprofile=null;

StringTokenizer profiles=new StringTokenizer(profile,",");
while(profiles.hasMoreTokens()){
String nextProfile=profiles.nextToken();

//extract value
int index=nextProfile.indexOf("=");

if(index==-1)
throw new RuntimeException("Wrong parameter in "+profile
+"\nPlease use comma-separated set of uses such as YCC=/desktop/
ycc.pf,CMY=/desktop/cmy.pf");

String key=nextProfile.substring(0,index).trim();
String value=nextProfile.substring(index+1).trim();

if(key.equals("YCC"))
YCCprofile=value;
else if(key.equals("CMY"))
CMYprofile=value;
else if(key.equals("CMYK"))
CMYKprofile=value;
else
throw new RuntimeException("Unknown parameter in
"+profile+"\nPlease use comma-separated set of uses such as YCC= CMY=

or (optional value) CMYK=");

}

/**
* set colorspaces and color models using profiles ifn ot
previously initialised
*/
if(CMYK==null){

//optional alternative CMYK
if(CMYKprofile==null)
CMYK=new DeviceCMYKColorSpace().getColorSpace();
else{
try {
CMYK=new
ICC_ColorSpace(ICC_Profile.getInstance(new
FileInputStream(CMYKprofile)));
} catch (Exception e) {
throw new RuntimeException("Unable to create CMYK

colorspace with "+CMYKprofile+"\nPlease check Path and file valid or

use built-in");
}
}

if(YCCprofile==null){
throw new RuntimeException("Mandatory YCC value not
supplied in "+profile+"\nPlease add ,YCC=yourPath/yourYCCprofile.pf");
}else{
try {
YCC=new
ICC_ColorSpace(ICC_Profile.getInstance(new
FileInputStream(YCCprofile)));
} catch (Exception e) {
throw new RuntimeException("Unable to create YCC

colorspace with "+YCCprofile+"\nPlease check Path and file valid");
}
}

if(CMYprofile==null){
throw new RuntimeException("Mandatory CMY value not
supplied in "+profile+"\nPlease add ,CMY=yourPath/yourCMYprofile.pf");
}else{
try {
CMY=new
ICC_ColorSpace(ICC_Profile.getInstance(new
FileInputStream(CMYprofile)));
} catch (Exception e) {
throw new RuntimeException("Unable to create CMY

colorspace with "+CMYprofile+"\nPlease check Path and file valid");
}
}

//needed for sRGB conversion
rgbCS = new
ICC_ColorSpace(ICC_Profile.getInstance(ColorSpace.CS_sRGB));
rgbModel = new ComponentColorModel(rgbCS, new int[] { 8,

8, 8 }, false, false, ColorModel.OPAQUE, DataBuffer.TYPE_BYTE);
CSToRGB = new ColorConvertOp(CMYK, rgbCS,
ColorSpaces.hints);

}

int pixelCount = w * h*4;
int Y,Cb,Cr,CENTER,lastY=-1,lastCb=-1,lastCr=-1,lastCENTER=-1
;
float[] CMYvalues=new float[3],YCCvalues,CIEvalues;

int cachedValues =0;
Map cache =new HashMap();

//turn YCC in Buffer to CYM using profile
for (int i = 0; i < pixelCount; i = i + 4) {

Y=(buffer[i] & 255);
Cb = (buffer[i+1] & 255);
Cr = (buffer[i+2] & 255);
CENTER = (buffer[i+3] & 255);

if(Y==lastY && Cb==lastCb && Cr==lastCr &&
CENTER==lastCENTER){
//no change so use last value
}else{ //new value

//we store values to speedup operation
Integer key=Integer.valueOf(Y+(Cb<<8)+(Cr<<16));
Object cachedValue= cache.get(key);

if(cachedValue==null){

//this is the slow bit
YCCvalues= new float[]{(Y / 255f), (Cb / 255f),
(Cr / 255f)};
CIEvalues=YCC.toCIEXYZ(YCCvalues);
CMYvalues=CMY.fromCIEXYZ(CIEvalues);

//stop cache taking up too much memory by
flushing if too many values
if(cachedValues >1000){
cachedValues =0;
cache.clear();
}
cache.put(key,CMYvalues);

}else
CMYvalues=(float[]) cachedValue;

//flag so we can just reuse if next value the same
lastY=Y;
lastCb=Cb;
lastCr=Cr;
lastCENTER=CENTER;
}

//put back as CMY
buffer[i]=(byte)(CMYvalues[0]*255);
buffer[i+1]=(byte)(CMYvalues[1]*255);
buffer[i+2]=(byte)(CMYvalues[2]*255);
}

/**
* create CMYK raster from buffer
*/
Raster raster = Raster.createInterleavedRaster(new
DataBufferByte(buffer,buffer.length), w,h,w * 4,4, new int[]{ 0, 1, 2,

3 },null);
WritableRaster rgbRaster
=rgbModel.createCompatibleWritableRaster(w, h);

/**
* convert to sRGB fwith profiles (I think this is done
native as its much faster than my pure Java efforts)
*/
CSToRGB.filter(raster, rgbRaster);

//data now sRGB so create image
BufferedImage image =new
BufferedImage(w,h,BufferedImage.TYPE_INT_RGB);
image.setData(rgbRaster);

return image;
}

===========================================================================
To unsubscribe, send email to listserv@java.sun.com and include in the body
of the message "signoff JAVA2D-INTEREST". For general help, send email to
listserv@java.sun.com and include in the body of the message "help".