50

I'm having problems reading this one JPEG file using ImageIO.read(File file) - it throws an exception with the message "Unsupported Image Type".

I have tried other JPEG images, and they seem to work fine.

The only differance I've been able to spot is that this file seems to include a thumbnail - is that known to cause problems with ImageIO.read()?

Troublesome image

EDIT:

Added the resulting image:

Strange colors

Eddie Curtis
  • 1,207
  • 8
  • 20
Malakim
  • 1,333
  • 2
  • 18
  • 34

6 Answers6

55

Old post, but for future reference:

Inspired by this question and links found here, I've written a JPEGImageReader plugin for ImageIO that supports CMYK color models (both with original color model, or implicitly converted to RGB on read). The reader also does proper color conversion, using the ICC profile embedded in the JPEG stream, in contrast to other solutions mentioned here.

It's plain Java and does not require JAI. The source code and binary distributions are freely available at github.com/haraldk/TwelveMonkeys, and is covered by a BSD-style license.

Once you have it installed, it allows you to read CMYK JPEGs using ImageIO.read(...) like this:

File cmykJPEGFile = new File(/*path*/);
BufferedImage image = ImageIO.read(cmykJPEGFile);

I.e.: In most cases, it's not necessary to modify your code.

Harald K
  • 26,314
  • 7
  • 65
  • 111
  • 1
    Thanks! I was wishing for something like this. Do you have a readme / docs? :) or should I just check the tests? Thanks again – Eran Medan Apr 26 '13 at 21:20
  • 4
    Sorry, documentation is sparse at the moment. However, it's an ImageIO plugin, so if you just want to read a CMYK JPEG do the following: Build the JARs using Maven, place in classpath and ImageIO.read(cmykJPEGFile) should just work. Feel free to ask, if there's anything specific you'd like to do. :-) – Harald K May 08 '13 at 14:21
  • @haraldk Thanks! I've thrown the .jars in /lib and my unit test is passing verifying that the reader does get returned but there's also another reader returned `com.sun.imageio.plugins.jpeg.JPEGImageReader` how do I know which is selected at runtime with `ImageIO.read` ? – fIwJlxSzApHEZIl Apr 17 '17 at 18:11
  • 1
    @anon58192932 `ImageIO.read()` will use the first `ImageReader` capable of reading the input. When the TwelveMonkeys plugin is registered, it makes sure that it is always [ordered](https://docs.oracle.com/javase/7/docs/api/javax/imageio/spi/ServiceRegistry.html#setOrdering(java.lang.Class,%20T,%20T)) *before* the `com.sun...JPEGImageReader`. So, if these are the only plugins you see, the TwelveMonkeys one should be selected, always (and should always be the first in the iterator in your test). – Harald K Apr 17 '17 at 18:48
  • 1
    This library works great, Both ``Unsupported Image Type`` and ``java.lang.IllegalArgumentException: Numbers of source Raster bands and source color space components do not match`` exceptions stopped showing up. – fall Jun 01 '17 at 08:28
41

Your image "Color Model" is CMYK, JPEGImageReader (the inner class that reads your file) reads only RGB Color Model.

If you insist on reading CMYK images, then you will need to convert them, try this code.

UPDATE

Read a CMYK image into RGB BufferedImage.

    File f = new File("/path/imagefile.jpg");

    //Find a suitable ImageReader
    Iterator readers = ImageIO.getImageReadersByFormatName("JPEG");
    ImageReader reader = null;
    while(readers.hasNext()) {
        reader = (ImageReader)readers.next();
        if(reader.canReadRaster()) {
            break;
        }
    }

    //Stream the image file (the original CMYK image)
    ImageInputStream input =   ImageIO.createImageInputStream(f); 
    reader.setInput(input); 

    //Read the image raster
    Raster raster = reader.readRaster(0, null); 

    //Create a new RGB image
    BufferedImage bi = new BufferedImage(raster.getWidth(), raster.getHeight(), 
    BufferedImage.TYPE_4BYTE_ABGR); 

    //Fill the new image with the old raster
    bi.getRaster().setRect(raster);

UPDATE - March 2015 - Adding simulation images

Original images were removed from OP's dropbox. So I'm adding new images (not the originals) that simulates the problem that was happening with them.

First image is how a normal RGB image looks like.

Image RGB

Second image is how the same image will look like in CMYK color model.

You cannot actually see how it looks on the web because it will be converted to RGB by the host. To see exactly how it looks, take the RGB image and run it through an RGB to CMYK converter.

Third image is how the CMYK image will look like when read then written using Java ImageIO.

Image CMYK read through Java RGB

The problem that was happening with OP is they had something like image 2 which throws an exception when you try to read it.

mohdajami
  • 9,604
  • 3
  • 32
  • 53
  • Excellent, I will give it a try. Will it work for RGB images as well, or will I need to detect the type somehow? – Malakim Mar 09 '10 at 12:40
  • You will find many ways to detect the color model, my preferred is using JPEGImageReader, if it throws `Unsupported Image Type` exception then its most probably CMYK. – mohdajami Mar 09 '10 at 12:47
  • 1
    This works well enough, however, the colors gets all messed up. See the new image I've attached to the question. Do you have any advice regarding this? Thanks! – Malakim Mar 09 '10 at 18:30
  • Yea, the color bleed is usual. Maybe you should try to use Javas `ColorConvertOp`, but not sure if it will help either. There is many ways of converting from one color model to another if thats your interest. I only gave the simplest one. If its a one time image, i would say just save it to RGB model in a professional image editing software. – mohdajami Mar 10 '10 at 07:10
  • OK, problem is I don't have control over what images the users will upload. So I guess I'll need to look deeper into the color model conversions to support any JPEG's the users might throw at the application. Thank you so much for the help. – Malakim Mar 10 '10 at 07:31
  • in that case you should make a function to check the image model before processing it. Check this http://forums.sun.com/thread.jspa?threadID=5391316 good luck :) – mohdajami Mar 10 '10 at 07:56
  • +1 Thanks for the solution. Now work on color model conversion. – torak Apr 13 '10 at 18:14
  • this fix is ok, but it threw `ArrayIndexOutOfBoundsException` on `bi.getRaster().setRect(raster);` when processing a 2nd image. – FunnyJava Jun 22 '18 at 08:58
19

I'm a bit late to the party. But it's probably still worth that I post my answer as none of the answers really solves the problem.

The solution requires Sanselan (or Apache Commons Imaging as it's called now) and it requires a reasonable CMYK color profile (.icc file). You can get the later one from Adobe or from eci.org.

The basic problem is that Java - out of the box - can only read JPEG files in RGB. If you have a CMYK file, you need to distinguish between regular CMYK, Adobe CMYK (with inverted values, i.e. 255 for no ink and 0 for maximum ink) and Adobe CYYK (some variant with inverted colors as well).

public class JpegReader {

    public static final int COLOR_TYPE_RGB = 1;
    public static final int COLOR_TYPE_CMYK = 2;
    public static final int COLOR_TYPE_YCCK = 3;

    private int colorType = COLOR_TYPE_RGB;
    private boolean hasAdobeMarker = false;

    public BufferedImage readImage(File file) throws IOException, ImageReadException {
        colorType = COLOR_TYPE_RGB;
        hasAdobeMarker = false;

        ImageInputStream stream = ImageIO.createImageInputStream(file);
        Iterator<ImageReader> iter = ImageIO.getImageReaders(stream);
        while (iter.hasNext()) {
            ImageReader reader = iter.next();
            reader.setInput(stream);

            BufferedImage image;
            ICC_Profile profile = null;
            try {
                image = reader.read(0);
            } catch (IIOException e) {
                colorType = COLOR_TYPE_CMYK;
                checkAdobeMarker(file);
                profile = Sanselan.getICCProfile(file);
                WritableRaster raster = (WritableRaster) reader.readRaster(0, null);
                if (colorType == COLOR_TYPE_YCCK)
                    convertYcckToCmyk(raster);
                if (hasAdobeMarker)
                    convertInvertedColors(raster);
                image = convertCmykToRgb(raster, profile);
            }

            return image;
        }

        return null;
    }

    public void checkAdobeMarker(File file) throws IOException, ImageReadException {
        JpegImageParser parser = new JpegImageParser();
        ByteSource byteSource = new ByteSourceFile(file);
        @SuppressWarnings("rawtypes")
        ArrayList segments = parser.readSegments(byteSource, new int[] { 0xffee }, true);
        if (segments != null && segments.size() >= 1) {
            UnknownSegment app14Segment = (UnknownSegment) segments.get(0);
            byte[] data = app14Segment.bytes;
            if (data.length >= 12 && data[0] == 'A' && data[1] == 'd' && data[2] == 'o' && data[3] == 'b' && data[4] == 'e')
            {
                hasAdobeMarker = true;
                int transform = app14Segment.bytes[11] & 0xff;
                if (transform == 2)
                    colorType = COLOR_TYPE_YCCK;
            }
        }
    }

    public static void convertYcckToCmyk(WritableRaster raster) {
        int height = raster.getHeight();
        int width = raster.getWidth();
        int stride = width * 4;
        int[] pixelRow = new int[stride];
        for (int h = 0; h < height; h++) {
            raster.getPixels(0, h, width, 1, pixelRow);

            for (int x = 0; x < stride; x += 4) {
                int y = pixelRow[x];
                int cb = pixelRow[x + 1];
                int cr = pixelRow[x + 2];

                int c = (int) (y + 1.402 * cr - 178.956);
                int m = (int) (y - 0.34414 * cb - 0.71414 * cr + 135.95984);
                y = (int) (y + 1.772 * cb - 226.316);

                if (c < 0) c = 0; else if (c > 255) c = 255;
                if (m < 0) m = 0; else if (m > 255) m = 255;
                if (y < 0) y = 0; else if (y > 255) y = 255;

                pixelRow[x] = 255 - c;
                pixelRow[x + 1] = 255 - m;
                pixelRow[x + 2] = 255 - y;
            }

            raster.setPixels(0, h, width, 1, pixelRow);
        }
    }

    public static void convertInvertedColors(WritableRaster raster) {
        int height = raster.getHeight();
        int width = raster.getWidth();
        int stride = width * 4;
        int[] pixelRow = new int[stride];
        for (int h = 0; h < height; h++) {
            raster.getPixels(0, h, width, 1, pixelRow);
            for (int x = 0; x < stride; x++)
                pixelRow[x] = 255 - pixelRow[x];
            raster.setPixels(0, h, width, 1, pixelRow);
        }
    }

    public static BufferedImage convertCmykToRgb(Raster cmykRaster, ICC_Profile cmykProfile) throws IOException {
        if (cmykProfile == null)
            cmykProfile = ICC_Profile.getInstance(JpegReader.class.getResourceAsStream("/ISOcoated_v2_300_eci.icc"));
        ICC_ColorSpace cmykCS = new ICC_ColorSpace(cmykProfile);
        BufferedImage rgbImage = new BufferedImage(cmykRaster.getWidth(), cmykRaster.getHeight(), BufferedImage.TYPE_INT_RGB);
        WritableRaster rgbRaster = rgbImage.getRaster();
        ColorSpace rgbCS = rgbImage.getColorModel().getColorSpace();
        ColorConvertOp cmykToRgb = new ColorConvertOp(cmykCS, rgbCS, null);
        cmykToRgb.filter(cmykRaster, rgbRaster);
        return rgbImage;
    }
}

The code first tries to read the file using the regular method, which works for RGB files. If it fails, it reads the details of the color model (profile, Adobe marker, Adobe variant). Then it reads the raw pixel data (raster) and does all the necessary conversion (YCCK to CMYK, inverted colors, CMYK to RGB).

I'm not quite satisfied with my solution. While the colors are mostly good, dark areas are slightly too bright, in particular black isn't fully black. If anyone knows what I could improve, I'd be glad to hear it.

Codo
  • 75,595
  • 17
  • 168
  • 206
  • 1
    This is the best answer I've found on this, but don't you want to close the ImageInputStream in a finally block? – Alkanshel Nov 13 '15 at 23:34
  • Thanks for the code snippet, works fine. (ISOcoated_v2_300_eci.icc can be found here: http://www.humburg.de/?page=4 ) – user2198875 Jan 05 '17 at 16:29
7

ImageIO.read() ->

File filePath = new File("C:\\Users\\chang\\Desktop\\05036877.jpg");
com.sun.image.codec.jpeg.JPEGImageDecoder jpegDecoder =  JPEGCodec.createJPEGDecoder (new FileInputStream(filePath));

BufferedImage image = jpegDecoder.decodeAsBufferedImage();
Tisho
  • 8,320
  • 6
  • 44
  • 52
Peter
  • 91
  • 1
  • 5
  • 4
    From the API documentation: `Note that the classes in the com.sun.image.codec.jpeg package are not part of the core Java APIs. They are a part of Sun's JDK and JRE distributions. Although other licensees may choose to distribute these classes, developers cannot depend on their availability in non-Sun implementations. We expect that equivalent functionality will eventually be available in a core API or standard extension.` – Omertron Sep 17 '12 at 12:41
6

I found https://stackoverflow.com/questions/22409... on here as well, this one does a great colour conversion

And combined both to get this:

private BufferedImage convertCMYK2RGB(BufferedImage image) throws IOException{
    log.info("Converting a CYMK image to RGB");
    //Create a new RGB image
    BufferedImage rgbImage = new BufferedImage(image.getWidth(), image.getHeight(),
    BufferedImage.TYPE_3BYTE_BGR);
    // then do a funky color convert
    ColorConvertOp op = new ColorConvertOp(null);
    op.filter(image, rgbImage);
    return rgbImage;
}
Community
  • 1
  • 1
enkor
  • 7,527
  • 3
  • 31
  • 55
  • 1
    This was the only answer I found that fixes the issue with green tints on JPEGs that has several questions on SO. – Phil Aug 08 '16 at 19:12
0

i fix it by this. only need add this dependency. i can read CMYK image by ImageIO. TwelveMonkeys

ImageIO.read(new URL("http://img3.tianyancha.com/api/9b80a61183787909e719c77fd0f78103.png"))
yaoning
  • 121
  • 4