Converting the color space of an image for PDF consumption

This post is more than 2 years old.

Reader Morgan W. wrote in a few days ago with an interesting problem:

I'm having some troubles with ColdFusion and Images. I'm using cffile to upload images (typically in jpeg format) to the server. From there I'm using CFDocument to write a pdf including these images.

The problem is that the color space of the uploaded jpeg does not react well to cfdocument for some reason. I've been told by people at Adobe that all you have to do is 'Just use Java to change the color space' (and maybe convert it to a png) of the image and I won't have the problem any longer. Problem is, I have no idea how to 'Just use Java to change the colorspace'.

The first thing I recommended to Morgan was to try the simple PNG conversion. ColdFusion makes this pretty trivial. If you read in foo.jpg, and then save it as foo.png, the image is converted. There is no separate conversion function. Unfortunately, this didn't help with her PDFs.

As a bit more background on why this was important to Morgan, she noticed that when building a PDF by hand (I assume 'by hand' means with Acrobat and not needlepoint) she saw file sizes between 300K and 1 meg. When using CFDOCUMENT the file sizes ranged from 20-30 megs. That's a pretty huge difference.

So I did a bit of digging into color spaces and images. As always, the Java SDK API docs (http://java.sun.com/javase/6/docs/api/ is the URL I used last time I believe) is a good place to start.

After a lot of trial an error, I was able to get the following code working:

<cfset img = imageRead("ray.png")>

<cfset bimg = imageGetBufferedImage(img)> <cfset colorModel = bimg.getColorModel()> <cfset colorSpace = colorModel.getColorSpace()>

<!--- gray scale ---> <cfset gCS = createObject("java","java.awt.color.ColorSpace").CS_GRAY>

<cfset gray = createObject("java","java.awt.color.ColorSpace").getInstance(gCS)>

<cfset convert = createObject("java", "java.awt.image.ColorConvertOp").init(gray,javaCast("null",""))>

<cfset newImage = convert.filter(bimg, javaCast("null",""))>

<cfset newImageOb = imageNew(newImage)> <cfimage action="writeToBrowser" source="#img#"> <cfimage action="writeToBrowser" source="#newImageOb#">

<cfdump var="#img#"><cfdump var="#newImageOb#">

Line by line, and note, I will be guessing a bit here in terms of some of the Java code, this is what the template does. First I load in a source image. I then use getBufferedImage to read in the underlying Java object for the image. From that, I can get the colorModel and colorSpace. I didn't actually use them though so you could remove those lines from any real production code.

I create my own color space. From the docs for ColorSpace, I chose CS_GRAY since it represented a gray scale color and I figured it would be easy to eyeball the difference, if it worked. The first line simply gets the field whereas the second then gets an instance of the grayscale colorspace.

Next up, I create an instance of the COlorConvertOp class. As you can guess, this will handle doing color space conversions. The next line uses the filter operation to do just that.

Finally, I use imageNew to suck in the new buffered image and create a ColdFusion image out of it. I write both images out to the screen, and dump both as well to see if ColdFusion notices a difference.

The result:

You can see that the color space is definitely different in both images.

I wonder if this is would be a worthwhile addition to ImageUtils?

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Adam Tuttle posted on 6/8/2009 at 11:17 PM

It's not something I need any more, but for a while I was searching for a way to convert CMYK images to RGB. Any idea how to do that? I searched for a Java util to do it, but couldn't find anything.

Comment 2 by Morgan Wood posted on 6/8/2009 at 11:49 PM

I ended up actually converting the ColorSpace to CS_sRGB. For whatever reason CFDocument likes the jpg after it's been converted that way. The CFDocument PDFs that I am creating went from 2.3 megs (with 5 images) to 344k.

Such an improvement.

Thanks!

Comment 3 by Michele posted on 6/10/2009 at 5:22 AM

basic question - what is color space? The only difference I could see in the eexample was that one image was multcollored and the other had shades of grey.

Thanks.

Comment 4 by Raymond Camden posted on 6/10/2009 at 6:28 PM

The Internet is wonderful....

http://en.wikipedia.org/wik...

;)

Comment 5 by Neil Patterson posted on 2/10/2010 at 12:39 AM

Adam, I think I successfully used the example above to check if an image is CMYK then convert to CS_sRGB. This really helped me to speed up those nasty cmyk jpgs that seemed to take 15 minutes to resize. Just use the colorSpace Variable to check if the image is CMYK then do the exact same conversion as Ray describes above but subsitutue CS_GRAY for CS_sRGB. It works for me, not sure how practical it is yet though.

<cfset img = imageRead("ray.png")>
<cfset bimg = imageGetBufferedImage(img)>
<cfset colorModel = bimg.getColorModel()>
<cfset colorSpace = colorModel.getColorSpace().getProfile().getColorSpaceType()>

<!--- If Colorspace is 9 convert the CMYK image to rgb scale --->
<cfif colorSpace EQ 9>
<!--- convert to CS_sRGB here --->
</cfif>

Comment 6 by James Moberg posted on 3/30/2010 at 8:33 PM

Do you know if there is any way to "detect" a CMYK JPG apart from using CFTRY/CFCATCH to catch the error? It tried reading a CMYK JPG image to use the conversion and I get an "Invalid image format" error when reading the file. (The image is from a Pentax camera and works fine if I resave it using Paint.net/FastStone Photo Resizer.)

Comment 7 by Raymond Camden posted on 4/1/2010 at 1:31 AM

Doesn't the comment above yours kinda discuss it?

Comment 8 by James Moberg posted on 4/1/2010 at 4:42 AM

I get an error when trying to read a CMYK JPG image... If I can't read a CMYK JPG image without getting an error, I'm definitely not able to run the rest of the code.

Comment 9 by Neil posted on 4/1/2010 at 5:52 AM

if you get an error when reading a cmyk image, make sure you have the latest hotfixes. this is a common problem for fresh cf 8 installs without the hotfixes I think

Comment 10 by Daniel Budde posted on 3/10/2011 at 9:31 PM

I know this is an older post, but I hope you guys might have an idea. I have an oddity with Mac screen shots. Whenever I take a PNG image that was created from a Mac screen shot and attempt to perform image manipulations on it using CF8, the processes run very slow. If I take the same image, load it in GIMP, it tells me there is an embedded color profile named 'Display' and asks if I want to convert the image. If I do so and then process the image in CF8, it performs as normal.

So, I have attempted to correct this in CF8 with no success by converting the image to a different format or by using Ray's example above and neither seems to fix the issue for me. Anyone have any ideas?

Comment 11 by Raymond Camden posted on 3/11/2011 at 3:39 AM

You got me there. Best I can say is that I've seen this myself with random mac images. It was always a minority of images - but when CF hit them it would take FOREVER to do stuff.

Comment 12 by Andrew posted on 10/6/2011 at 8:29 PM

Ray Camden, you are a beautiful man.

Used this info to correct CMKY images in our image generation system...

I hope that Adobe will add a simple color space conversion function soon!

Comment 13 by Raymond Camden posted on 10/7/2011 at 6:29 AM

Glad to help.

Comment 14 by Claude Schnພgans posted on 3/1/2012 at 4:55 AM

Hi,
I found this page while I was looking for a solution to a problem I'm having with CFDOCUMENT, the problem being that some grey scale images look much lighter in the pdf document than the original.
So I wrote down some code to list all characteristics of these images, and it appears that they all have their color space = to "Any of the family of GREY color spaces". Other images in RGB mode, even if they only use a grey scale are correct.
So I said OK, I will convert all of then to RGB so that CFDOCUMENT has no problem.
But there I discovered that converting the images to RGB using your Java code produces exactly the same problem.
Conclusion, the bug is not with CFDOCUMENT, but with the Java conversion function that does not render grey levels properly going from GREY TO RGB.
Unless there are some parameters so get a better conversion?

Comment 15 by Claude Schnພgans posted on 3/1/2012 at 8:34 AM

OK, got it !
If I use CS_LINEAR_RGB instead of CS_sRGB, the converted image looks the same. Bingo !
Finally, the problem with CFDOCUMENT is that it will convert any greysacale image into RGB using CS_sRGB mode instead of CS_LINEAR_RGB. Too bad.
Now that I know, I can convert any image myself and use <cfimage action="writeToBrowser"... so that CFDOCUMENT will not try to convert it.

Comment 16 by Raymond Camden posted on 3/1/2012 at 8:38 AM

Thank you for posting your solution, Claude!

Comment 17 by clarke ulmer posted on 8/22/2012 at 5:10 AM

Ray, this post was totally brilliant! I re-purposed some of your code here on my blog http://clarkeulmer.com/load...

Comment 18 by Raymond Camden posted on 8/22/2012 at 5:37 AM

Always glad when a post helps. :)