Scaling JPEG images with ColdFusion

Helping out on #coldfusion on dalnet, the topic of resizing JPEG images without having to install a external Java lib came up.

There are 2 things that make this slightly tricky, but not all together impossible:

  1. Documentation for com.sun.image.codec.jpeg.JPEGCodec can be very difficult to find if you don't know where you are looking
  2. Scaling a BufferedImage produces a Image, which can't be encoded into JPEG, so you need to create a new BufferedImage with the given Image.

So, here is the code I used as a test case to take an image of a Nissan 350z and scale it down by a quarter:

<cfscript>
    //need this to do all the JPEG work
    JpegCodec = createObject("java", "com.sun.image.codec.jpeg.JPEGCodec");
   
    //reading in file
    inputStream = createObject("java", "java.io.FileInputStream").init(expandPath("nissan.jpg"));
   
    //pushing out the file
    outputStream = createObject("java", "java.io.FileOutputStream").init(expandPath("nissan1.jpg"));
   
    //decodes the JPEG
    decoder = jpegCodec.createJPEGDecoder(inputStream);
   
    //give us an image to scale
    image = decoder.decodeAsBufferedImage();
   
    //make it 1/4 the size
    height = round(image.getHeight() /4);
    width = round(image.getWidth() / 4);
   
    //this is java.awt.image.Image, but we need a java.awt.image.BufferedImage!
    scaledImage = image.getScaledInstance(JavaCast("int", width), JavaCast("int", height), image.SCALE_DEFAULT);
   
    //create a new BufferedImage
    newBufferedImage = createObject("java", "java.awt.image.BufferedImage").init(JavaCast("int", width), JavaCast("int", height), image.TYPE_INT_RGB);
   
    //draw the image on the buffered image
    graphics = newBufferedImage.createGraphics();
    graphics.drawImage(scaledImage, 0, 0, Javacast("null", ""));
   
    //encode it
    encoder = jpegCodec.createJPEGEncoder(outputStream);
    encoder.encode(newBufferedImage);
   
    //close off the resources
    outputStream.close();
</cfscript>

So there you have it – JPEG scaling without using an external Java library. Enjoy. 

Leave a Comment

Comments

  • kEy | November 28, 2005

    Error Occurred While Processing Request
    JavaCast Type null must be one of the following types ("int","long","float","double","boolean","string")

    The error occurred in C:localhtdocslocalhostjpg.cfm: line 29

    27 : //draw the image on the buffered image
    28 : graphics = newBufferedImage.createGraphics();
    29 : graphics.drawImage(scaledImage, 0, 0, Javacast("null", 0));
    30 :
    31 : //encode it

    Please try the following:

    * Check the ColdFusion documentation to verify that you are using the correct syntax.
    * Search the Knowledge Base to find a solution to your problem.

    Browser Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8) Gecko/20051111 Firefox/1.5
    Remote Address 127.0.0.1
    Referrer
    Date/Time 28-Nov-05 02:38 PM
    Stack Trace
    at cfjpg2ecfm1623948356.runPage(C:localhtdocslocalhostjpg.cfm:29)

    coldfusion.runtime.JavaCastException: JavaCast Type null must be one of the following types ("int","long","float","double","boolean","string")
    at coldfusion.runtime.CFPage.JavaCast(CFPage.java:5376)
    at cfjpg2ecfm1623948356.runPage(C:localhtdocslocalhostjpg.cfm:29)
    at coldfusion.runtime.CfJspPage.invoke(CfJspPage.java:147)
    at coldfusion.tagext.lang.IncludeTag.doStartTag(IncludeTag.java:357)
    at coldfusion.filter.CfincludeFilter.invoke(CfincludeFilter.java:62)
    at coldfusion.filter.ApplicationFilter.invoke(ApplicationFilter.java:107)
    at coldfusion.filter.PathFilter.invoke(PathFilter.java:80)
    at coldfusion.filter.ExceptionFilter.invoke(ExceptionFilter.java:47)
    at coldfusion.filter.ClientScopePersistenceFilter.invoke(ClientScopePersistenceFilter.java:28)
    at coldfusion.filter.BrowserFilter.invoke(BrowserFilter.java:35)
    at coldfusion.filter.GlobalsFilter.invoke(GlobalsFilter.java:43)
    at coldfusion.filter.DatasourceFilter.invoke(DatasourceFilter.java:22)
    at coldfusion.CfmServlet.service(CfmServlet.java:105)
    at jrun.servlet.ServletInvoker.invoke(ServletInvoker.java:91)
    at jrun.servlet.JRunInvokerChain.invokeNext(JRunInvokerChain.java:42)
    at jrun.servlet.JRunRequestDispatcher.invoke(JRunRequestDispatcher.java:252)
    at jrun.servlet.ServletEngineService.dispatch(ServletEngineService.java:527)
    at jrun.servlet.jrpp.JRunProxyService.invokeRunnable(JRunProxyService.java:192)
    at jrunx.scheduler.ThreadPool$DownstreamMetrics.invokeRunnable(ThreadPool.java:348)
    at jrunx.scheduler.ThreadPool$ThreadThrottle.invokeRunnable(ThreadPool.java:451)
    at jrunx.scheduler.ThreadPool$UpstreamMetrics.invokeRunnable(ThreadPool.java:294)
    at jrunx.scheduler.WorkerThread.run(WorkerThread.java:66)

  • dan | November 28, 2005

    This code works fine for me, what version of cf are you using? I am using 7 and the cf docs show null as being a valid argument. If you take a look back to the 6.1 docs though it does not show null as a valid argument, so maybe thats the problem.

    You need a null here though because the methods 4th argument is looking for type ImageObserver and we are not passing it one. Still strange though because the docs for 7 include this statement

    Warning: Do not assign the results of JavaCast("null","") to a ColdFusion variable. Unexpected results will occur.

    Mark got any ideas?

  • dan | November 28, 2005

    Ahhh, nevermind, we are not casting a coldfusion variable, this should work fine in 7. Im not sure about 6.1 though

  • Mark | November 28, 2005

    Yep, you are correct, it’s a CF7 code.

    To c/p out of the docs:
    A ColdFusion variable that holds a scalar or string type. Must be "" if type is null.

    So if you are using 6.1, you will have to come up with an alternate way to get a null value (not sure how?).

    Mind you it was code I had whipped up in about 15 minutes, just to prove a concept….

  • kEy | November 28, 2005

    Yeap, there is no JavaCast type NULL in CF 6.1 while it works with CF7!

  • Massimo Foti | November 28, 2005

    There are a few CFC available that don’t require external Java libraries. You can try one here:
    http://www.olimpo.ch/tmt/cfc/tmt_img/

  • dan | November 28, 2005

    I think mark was just showing an example of how to use this particular one. Plus the error we were getting above was coming from the java.awt package, a standard package that is included.

  • noname | November 29, 2005

    Nice script 🙂 I’ve been wondering if something like this was possible without installing a package. Great job! and thanks for the post.

  • Ryan Guill | November 29, 2005

    Thanks for picking this up!

    So how would you do it if you wanted to make it a certain pixel width to proportion? Is there a method you can call to make it resize it to a certain width?

  • Mark | November 29, 2005

    Ryan,
    This would be handled when you decide what width and height you want the newly sized Image to be – i.e. here:
    //make it 1/4 the size
    height = round(image.getHeight() /4);
    width = round(image.getWidth() / 4);

    //this is java.awt.image.Image, but we need a java.awt.image.BufferedImage!
    scaledImage = image.getScaledInstance(JavaCast("int", width), JavaCast("int", height), image.SCALE_DEFAULT);

    Technically the scaled width and height can be whatever you want, so nothing is stopping you from delving into some Math and working out what the height and width are, and then making sure that the values are in proportion to the original values.

    I’ve just made the image quarter of a size for demonstrative purposes.

  • Ryan Guill | November 29, 2005

    I thought you might have to do some math for that, I was just wondering if there was a method that just took straight pixels. This will work though. Thanks!

  • Seth | August 19, 2006

    It’s really nice to be able to control the output JPG qaulity, inserting this code will do that.

    //encode it
    encoder = jpegCodec.createJPEGEncoder(outputStream);
    //create quality param
    param = encoder.getDefaultJPEGEncodeParam(newBufferedImage);
    //assign JPG quality 0-1
    param.setQuality(JavaCast("float",.82),true);
    //set JPG quality
    encoder.setJPEGEncodeParam(param);
    encoder.encode(newBufferedImage);
    //close off the resources
    outputStream.close();

    Thanks for this great example

  • Happy user | February 15, 2007

    Below the outputStream.close(); the following code could/should be inserted.

    // Close the connection to the original image
    inputStream.close();

    Thank you for your example and keep up the good work

  • Maria Cuervo | June 2, 2007

    The below code works in Cold Fusion 6. I wrote it as a function. Function calls are below the code and can be customized.

    <cfscript>

    function resizeImg(imgid,width,height,type) {

    serverdir = "d:inetpubwhpd_images";
    imgInputPath = serverdir & "full" & imgid & ".jpg";
    imgOutputPath = serverdir & "" & type & "" & imgid & ".jpg";
    //WriteOutput("output: " & imgOutputPath) & "<br>";
    //WriteOutput("input: " & imgInputPath);

    // for JPEG work

    JpegCodec = createObject("java", "com.sun.image.codec.jpeg.JPEGCodec");

    //reading in file
    inputStream = createObject("java", "java.io.FileInputStream").init(imgInputPath);

    // output destination
    outputStream = createObject("java", "java.io.FileOutputStream").init(imgOutputPath);

    //decodes the JPEG
    decoder = jpegCodec.createJPEGDecoder(inputStream);

    //give us an image to scale
    image = decoder.decodeAsBufferedImage();

    //this is java.awt.image.Image, but we need a java.awt.image.BufferedImage!
    scaledImage = image.getScaledInstance(JavaCast("int", width), JavaCast("int", height), image.SCALE_DEFAULT);

    //create a new BufferedImage
    newBufferedImage = createObject("java", "java.awt.image.BufferedImage").init(JavaCast("int", width), JavaCast("int", height), image.TYPE_INT_RGB);

    //draw the image on the buffered image
    graphics = newBufferedImage.createGraphics();

    imageObserver = createObject("java", "java.awt.Button").init();
    //this was a dummy object since java doesn’t let me pass a null.

    graphics.drawImage(scaledImage, 0, 0, imageObserver);
    // Javacast("null", "")); null is not a valid argument in 6.1
    // this line would have gone where imageObserver is now.

    //encode it
    encoder = jpegCodec.createJPEGEncoder(outputStream);
    encoder.encode(newBufferedImage);

    //close off the resources
    outputStream.close();
    }
    </cfscript>

    <!— create a thumbnail—>
    <cfset thumbnail = resizeImg(ufnum, tn_w, tn_h, "tn")>

    <!— create a med res web size —>
    <cfset websize = resizeImg(ufnum, wb_w, wb_h, "web")>

  • Maciek Pankiewicz | July 24, 2007

    Thanks for a nice script 😉

  • Mike | December 6, 2007

    I love you, can you have my babies!

  • Gus D | May 16, 2008

    I have used this code and scaled images and it all works like a wheel but I want to stream the scaled image to the web page. I want to dump the scaled image as a variable which I can feed to the <img src=#X#> tag
    I am currently using JAVAX.imageio.imageIO object to handle file writing …

    Anybody got any ideas in advance …