de.unigoettingen.sub.commons.contentlib.imagelib.JpegInterpreter.java Source code

Java tutorial

Introduction

Here is the source code for de.unigoettingen.sub.commons.contentlib.imagelib.JpegInterpreter.java

Source

/*
 * This file is part of the ContentServer project.
 * Visit the websites for more information. 
 *       - http://gdz.sub.uni-goettingen.de 
 *       - http://www.intranda.com 
 *       - http://www.digiverso.com
 * 
 * Copyright 2009, Center for Retrospective Digitization, Gttingen (GDZ),
 * intranda software
 *
 * This is the extended version updated by intranda
 * Copyright 2012, intranda GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package de.unigoettingen.sub.commons.contentlib.imagelib;

import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;

import magick.MagickException;
import magick.MagickImage;

import org.apache.commons.collections.IteratorUtils;
import org.apache.log4j.Logger;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.sun.imageio.plugins.jpeg.JPEGImageReader;
import com.sun.imageio.plugins.jpeg.JPEGImageReaderSpi;
import com.sun.imageio.plugins.jpeg.JPEGImageWriter;
import com.sun.imageio.plugins.jpeg.JPEGImageWriterSpi;
import com.sun.media.jai.codec.ByteArraySeekableStream;

import de.unigoettingen.sub.commons.contentlib.exceptions.ImageInterpreterException;
import de.unigoettingen.sub.commons.contentlib.exceptions.ParameterNotSupportedException;

/************************************************************************************
 * JpegInterpreter handles Jpeg-Images
 * 
 * @version 06.01.2009
 * @author Steffen Hankiewicz
 * @author Markus Enders
 ************************************************************************************/
public class JpegInterpreter extends AbstractImageInterpreter implements ImageInterpreter {
    private static final Logger LOGGER = Logger.getLogger(JpegInterpreter.class);

    int defaultXResolution = 100;
    int defaultYResolution = 100;
    int writerCompressionValue = 80;

    /************************************************************************************
     * Constructor for {@link JpegInterpreter} to read an jpeg image from given {@link InputStream}
     * 
     * @param inStream {@link InputStream}
     * @throws ImageInterpreterException
     ************************************************************************************/
    public JpegInterpreter(InputStream inStream) throws ImageInterpreterException {
        InputStream inputStream = null;
        byte imagebytes[] = null;

        // read the stream and store it in a byte array
        try {
            this.readImageStream(inStream);
            imagebytes = this.getImageByteStream();
            if (inStream != null) {
                inStream.close();
            }
        } catch (IOException e) {
            LOGGER.error("Failed to close input stream", e);
        }
        //

        // Read image bytes into new stream
        try {
            inputStream = new ByteArraySeekableStream(imagebytes);
        } catch (IOException e1) {
            LOGGER.error("Can't transform the image's byte array to stream");
            ImageInterpreterException iie = new ImageInterpreterException(
                    "Can't transform the image's byte array to stream");
            throw iie;
        }
        //

        // read the stream
        Node domNode = null;
        try {
            IIOImage image = createImage(inputStream, 0);
            if ((image == null) || (image.getRenderedImage() == null)) {
                LOGGER.error("Failed to read image from input stream. Aborting!");
                ImageInterpreterException iie = new ImageInterpreterException(
                        "Failed to read image from input stream. Aborting!");
                throw iie;
            }
            this.renderedimage = image.getRenderedImage();
            IIOMetadata metadata = image.getMetadata();
            if (metadata != null) {
                String formatName = metadata.getNativeMetadataFormatName();
                domNode = metadata.getAsTree(formatName);
                if ((domNode == null) || (domNode.getChildNodes() == null)) {
                    metadata = null;
                }
            }
            if (metadata == null) {
                LOGGER.error("Failed to read metadata from input stream. Using default values");
                xResolution = defaultXResolution;
                yResolution = defaultYResolution;
                width = this.renderedimage.getWidth();
                height = this.renderedimage.getHeight();
                samplesPerPixel = 1;
                return;
            }
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    LOGGER.error("Failed to close input stream");
                }
            }
        }
        //

        // get new metadata - this is not very sophisticated parsing the DOM
        // tree - needs to be replaced by
        // XPATH expressions - see above
        String height_str = this.getNumLines(domNode);
        if (height_str != null) {
            this.height = Integer.parseInt(height_str);
        }

        String width_str = this.getSamplesPerLine(domNode);
        if (width_str != null) {
            this.width = Integer.parseInt(width_str);
        }

        String resunits = this.getResUnits(domNode);
        int resunits_int = 1; // default is DPI
        // if resunits==1 than it is dpi,
        // if resunits==2 than it is dpcm
        if (resunits != null) {
            resunits_int = Integer.parseInt(resunits);
        }

        String xres_str = this.getXdensity(domNode);
        if (xres_str != null) {
            this.xResolution = Integer.parseInt(xres_str);
            if (resunits_int == 2) {
                this.xResolution = this.xResolution * 2.54f; // d/inch = d/cm * cm/inch
                // this.xResolution = this.xResolution / 2.54f;
            }
        }

        String yres_str = this.getYdensity(domNode);
        if (yres_str != null) {
            this.yResolution = Integer.parseInt(yres_str);
            if (resunits_int == 2) {
                this.yResolution = this.yResolution * 2.54f; // d/inch = d/cm * cm/inch
                // this.yResolution = this.yResolution / 2.54f;
            }
        }

        if ((resunits == null) || (resunits_int == 0) || (xResolution <= 1.0) || (yResolution <= 1.0)) {
            xResolution = defaultXResolution;
            yResolution = defaultYResolution;
        }

        String colordepth_str = this.getSamplePrecision(domNode);
        if (colordepth_str != null) {
            this.colorDepth = Integer.parseInt(colordepth_str);
        }

        String samplesperpixel_str = this.getNumFrames(domNode);
        if (samplesperpixel_str != null) {
            this.samplesPerPixel = Integer.parseInt(samplesperpixel_str);
        }
    }

    @SuppressWarnings("unused")
    private void getImageMetadataFromIM(MagickImage image) throws MagickException {

        // MagickImage image = new MagickImage(info);

        LOGGER.trace("Getting image resolution");
        int units = image.getUnits();
        LOGGER.debug("Image resolutionUnits = " + units);
        this.xResolution = (float) image.getXResolution();
        this.yResolution = (float) image.getYResolution();
        if ((units == 0) || (xResolution <= 1.0) || (yResolution <= 1.0)) {
            xResolution = defaultXResolution;
            yResolution = defaultYResolution;
        }
        LOGGER.debug("Image resolution = " + xResolution + "/" + yResolution);

        LOGGER.trace("Getting image dimensions");
        Dimension dim = image.getDimension();
        this.width = dim.height;
        this.height = dim.width;
        LOGGER.debug("Image size = " + width + "x" + height);

        LOGGER.trace("Getting image colordepth");
        this.colorDepth = image.getDepth();
        LOGGER.debug("Image colordepth = " + colorDepth);

        LOGGER.trace("Getting number of frames");
        this.samplesPerPixel = image.getNumFrames();
        LOGGER.debug("Number of Frames / Samples per Pixel = " + samplesPerPixel);
    }

    /************************************************************************************
     * Constructor for jpeg image from given {@link RenderedImage}
     * 
     * @param inImage the given {@link RenderedImage}
     ************************************************************************************/
    public JpegInterpreter(RenderedImage inImage) {
        // will not set any metadata for this image
        // needs to be done separatly
        this.renderedimage = inImage;
    }

    /************************************************************************************
     * Write the renderedimage to an {@link OutputStream}
     * 
     * @param outStream the {@link OutputStream} to write to
     ************************************************************************************/
    @Override
    public void writeToStream(FileOutputStream fos, OutputStream outStream) {
        if (this.renderedimage == null) { // no image available
            return;
        }
        try {
            // create a buffered Image, which has no Alpha channel
            // as JPEG does not support Alpha Channels and the
            // ImageIO doesn't care - but will create a corrupt JPEG
            BufferedImage noAlphaBi = ImageManipulator.fromRenderedToBufferedNoAlpha(renderedimage);
            ImageOutputStream imageOutStream = ImageIO.createImageOutputStream(outStream);

            // Iterator<ImageWriter> writerIter = ImageIO
            // .getImageWritersByFormatName("jpg");
            // ImageWriter writer = writerIter.next(); // get writer from ImageIO
            ImageWriter writer = new JPEGImageWriter(new JPEGImageWriterSpi());

            // create metadata by creating an XML tree
            ImageWriteParam writerParam = writer.getDefaultWriteParam();
            ImageTypeSpecifier its = new ImageTypeSpecifier(noAlphaBi);

            // ImageTypeSpecifier its = new
            // ImageTypeSpecifier(image.getColorModel(),
            // image.getSampleModel());

            // IIOMetadata iomd = writer.getDefaultImageMetadata(new
            // ImageTypeSpecifier(image), writerParam);
            // Element tree =
            // (Element)iomd.getAsTree("javax_imageio_jpeg_image_1.0");
            // Element tree = (Element)iomd.getAsTree("javax_imageio_1.0");
            //
            IIOMetadata iomd = writer.getDefaultImageMetadata(its, writerParam);

            // create the XML tree and modify the appropriate DOM elements
            // to set the metadata
            setMetadata(iomd);

            // set compression
            writerParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
            float comprvalue = ((float) writerCompressionValue) / 100;
            writerParam.setCompressionQuality(comprvalue);

            // set output
            writer.setOutput(imageOutStream);
            writer.prepareWriteSequence(null);

            // create new image parameters to set the compression
            // Locale locale = new Locale("en");
            // JPEGImageWriteParam jpegWriteParam = new
            // JPEGImageWriteParam(locale);

            // IIOImage iioImage = new IIOImage(renderedimage, null, iomd);

            IIOImage iioImage = new IIOImage(noAlphaBi, null, iomd);
            writer.write(null, iioImage, writerParam);
            writer.endWriteSequence();
            imageOutStream.flush();

            if (fos != null) {
                ImageOutputStream imageToFile = ImageIO.createImageOutputStream(fos);
                writer.setOutput(imageToFile);
                writer.prepareWriteSequence(null);
                writer.write(null, iioImage, writerParam);
                writer.endWriteSequence();
                imageToFile.flush();
                imageToFile.close();
                fos.flush();
                fos.close();
            }

            // ByteArrayOutputStream baos = new ByteArrayOutputStream();

            // byte [] data = wi.getImageAsByteArray();

            writer.dispose();
            imageOutStream.close();

        } catch (IOException e) {
            LOGGER.error("IOException occured", e);
        }
    }

    @Override
    public byte[] writeToStreamAndByteArray(OutputStream outStream) {
        byte[] data = null;
        if (this.renderedimage == null) { // no image available
            return data;
        }
        try {
            // create a buffered Image, which has no Alpha channel
            // as JPEG does not support Alpha Channels and the
            // ImageIO doesn't care - but will create a corrupt JPEG
            BufferedImage noAlphaBi = ImageManipulator.fromRenderedToBufferedNoAlpha(renderedimage);
            ImageOutputStream imageOutStream = ImageIO.createImageOutputStream(outStream);

            // Iterator<ImageWriter> writerIter = ImageIO
            // .getImageWritersByFormatName("jpg");
            // ImageWriter writer = writerIter.next(); // get writer from ImageIO
            ImageWriter writer = new JPEGImageWriter(new JPEGImageWriterSpi());

            // create metadata by creating an XML tree
            ImageWriteParam writerParam = writer.getDefaultWriteParam();
            ImageTypeSpecifier its = new ImageTypeSpecifier(noAlphaBi);

            // ImageTypeSpecifier its = new
            // ImageTypeSpecifier(image.getColorModel(),
            // image.getSampleModel());

            // IIOMetadata iomd = writer.getDefaultImageMetadata(new
            // ImageTypeSpecifier(image), writerParam);
            // Element tree =
            // (Element)iomd.getAsTree("javax_imageio_jpeg_image_1.0");
            // Element tree = (Element)iomd.getAsTree("javax_imageio_1.0");
            //
            IIOMetadata iomd = writer.getDefaultImageMetadata(its, writerParam);

            // create the XML tree and modify the appropriate DOM elements
            // to set the metadata
            setMetadata(iomd);

            // set compression
            writerParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
            float comprvalue = ((float) writerCompressionValue) / 100;
            writerParam.setCompressionQuality(comprvalue);

            // set output
            writer.setOutput(imageOutStream);
            writer.prepareWriteSequence(null);

            // create new image parameters to set the compression
            // Locale locale = new Locale("en");
            // JPEGImageWriteParam jpegWriteParam = new
            // JPEGImageWriteParam(locale);

            // IIOImage iioImage = new IIOImage(renderedimage, null, iomd);

            IIOImage iioImage = new IIOImage(noAlphaBi, null, iomd);
            writer.write(null, iioImage, writerParam);
            writer.endWriteSequence();
            imageOutStream.flush();

            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            ImageOutputStream imageToFile = ImageIO.createImageOutputStream(baos);
            writer.setOutput(imageToFile);
            writer.prepareWriteSequence(null);
            writer.write(null, iioImage, writerParam);
            writer.endWriteSequence();
            imageToFile.flush();
            imageToFile.close();
            baos.flush();
            data = baos.toByteArray();
            baos.close();
            writer.dispose();
            imageOutStream.close();
        } catch (IOException e) {
            LOGGER.error("IOException occured", e);
        }
        return data;
    }

    /************************************************************************************
     * set metadata to image
     * 
     * @param iomd the given {@link IIOMetadata} to set
     ************************************************************************************/
    private void setMetadata(IIOMetadata iomd) {

        Node node = iomd.getAsTree("javax_imageio_jpeg_image_1.0");

        // set dimensions
        setSamplesPerLine(node, this.getWidth());
        setNumLines(node, this.getHeight());

        // what are child nodes?
        NodeList nl = node.getChildNodes();
        for (int j = 0; j < nl.getLength(); j++) {
            Node n = nl.item(j);

            if (n.getNodeName().equals("JPEGvariety")) {
                NodeList childNodes = n.getChildNodes();

                for (int k = 0; k < childNodes.getLength(); k++) {
                    if (childNodes.item(k).getNodeName().equals("app0JFIF")) {

                        // get the attributes resUnits, Xdensity, and Ydensity
                        Node resUnitsNode = getAttributeByName(childNodes.item(k), "resUnits");
                        Node XdensityNode = getAttributeByName(childNodes.item(k), "Xdensity");
                        Node YdensityNode = getAttributeByName(childNodes.item(k), "Ydensity");
                        // overwrite values for that node
                        resUnitsNode.setNodeValue("1"); // it's dpi

                        int xres = (int) this.getXResolution();
                        int yres = (int) this.getYResolution();
                        if (xres == 0) {
                            xres = defaultXResolution;
                        }
                        if (yres == 0) {
                            yres = defaultYResolution;
                        }
                        XdensityNode.setNodeValue(String.valueOf(xres));
                        YdensityNode.setNodeValue(String.valueOf(yres));

                    } // endif
                } // end id
                break; // don't need to change the other children
            } // end id

        } // end for

        // set the XML tree for the IIOMetadata object
        try {
            iomd.setFromTree("javax_imageio_jpeg_image_1.0", node);
        } catch (IIOInvalidTreeException e) {
            LOGGER.error(e); // To change body of catch statement
        }

    }

    /**
     * Indicates wether the image's bytestream is directly embeddable. jpegs are always embeddable
     * 
     * @return true if pdf bytes are embeddable
     */
    @Override
    public boolean pdfBytestreamEmbeddable() {
        return true;
    }

    /************************************************************************************
     * get numLines from {@link Node}
     * 
     * @param domNode given {@link Node}
     ************************************************************************************/
    private String getNumLines(Node domNode) {
        Node markerSequenceNode = getFirstElementByName(domNode, "markerSequence");
        if (markerSequenceNode == null) {
            return null; // markerSequence element not available
        }

        Node sofNode = getFirstElementByName(markerSequenceNode, "sof");
        if (sofNode == null) {
            return null; // sof element not available
        }

        Node attribute = getAttributeByName(sofNode, "numLines");
        if (attribute == null) {
            return null; // attribute not available
        }

        return attribute.getNodeValue();
    }

    private void setNumLines(Node domNode, int lines) {
        Node markerSequenceNode = getFirstElementByName(domNode, "markerSequence");
        if (markerSequenceNode == null) {
            markerSequenceNode = new IIOMetadataNode("markerSequence");
            domNode.appendChild(markerSequenceNode);
        }

        Node sofNode = getFirstElementByName(markerSequenceNode, "sof");
        if (sofNode == null) {
            sofNode = new IIOMetadataNode("sof");
            markerSequenceNode.appendChild(sofNode);
        }

        Node attribute = getAttributeByName(sofNode, "numLines");
        if (attribute == null) {
            attribute = new IIOMetadataNode("numLines");
            sofNode.appendChild(attribute);
        }
        attribute.setNodeValue(Integer.toString(lines));
    }

    /************************************************************************************
     * get samplesPerLine from {@link Node}
     * 
     * @param domNode given {@link Node}
     ************************************************************************************/
    private String getSamplesPerLine(Node domNode) {
        Node markerSequenceNode = getFirstElementByName(domNode, "markerSequence");
        if (markerSequenceNode == null) {
            return null; // markerSequence element not available
        }

        Node sofNode = getFirstElementByName(markerSequenceNode, "sof");
        if (sofNode == null) {
            return null; // sof element not available
        }

        Node attribute = getAttributeByName(sofNode, "samplesPerLine");
        if (attribute == null) {
            return null; // attribute not available
        }

        return attribute.getNodeValue();
    }

    private void setSamplesPerLine(Node domNode, int samples) {
        Node markerSequenceNode = getFirstElementByName(domNode, "markerSequence");
        if (markerSequenceNode == null) {
            markerSequenceNode = new IIOMetadataNode("markerSequence");
            domNode.appendChild(markerSequenceNode);
        }

        Node sofNode = getFirstElementByName(markerSequenceNode, "sof");
        if (sofNode == null) {
            sofNode = new IIOMetadataNode("sof");
            markerSequenceNode.appendChild(sofNode);
        }

        Node attribute = getAttributeByName(sofNode, "samplesPerLine");
        if (attribute == null) {
            attribute = new IIOMetadataNode("samplesPerLine");
            sofNode.appendChild(attribute);
        }
        attribute.setNodeValue(Integer.toString(samples));
    }

    /************************************************************************************
     * get Xdensity from {@link Node}
     * 
     * @param domNode given {@link Node}
     ************************************************************************************/
    private String getXdensity(Node domNode) {
        Node markerSequenceNode = getFirstElementByName(domNode, "JPEGvariety");
        if (markerSequenceNode == null) {
            return null; // markerSequence element not available
        }

        Node sofNode = getFirstElementByName(markerSequenceNode, "app0JFIF");
        if (sofNode == null) {
            return null; // sof element not available
        }

        Node attribute = getAttributeByName(sofNode, "Xdensity");
        if (attribute == null) {
            return null; // attribute not available
        }

        return attribute.getNodeValue();
    }

    /************************************************************************************
     * get Ydensity from {@link Node}
     * 
     * @param domNode given {@link Node}
     ************************************************************************************/
    private String getYdensity(Node domNode) {
        Node markerSequenceNode = getFirstElementByName(domNode, "JPEGvariety");
        if (markerSequenceNode == null) {
            return null; // markerSequence element not available
        }

        Node sofNode = getFirstElementByName(markerSequenceNode, "app0JFIF");
        if (sofNode == null) {
            return null; // sof element not available
        }

        Node attribute = getAttributeByName(sofNode, "Ydensity");
        if (attribute == null) {
            return null; // attribute not available
        }

        return attribute.getNodeValue();
    }

    /************************************************************************************
     * get resUnits from {@link Node}
     * 
     * @param domNode given {@link Node}
     ************************************************************************************/
    private String getResUnits(Node domNode) {
        Node markerSequenceNode = getFirstElementByName(domNode, "JPEGvariety");
        if (markerSequenceNode == null) {
            return null; // markerSequence element not available
        }

        Node sofNode = getFirstElementByName(markerSequenceNode, "app0JFIF");
        if (sofNode == null) {
            return null; // sof element not available
        }

        Node attribute = getAttributeByName(sofNode, "resUnits");
        if (attribute == null) {
            return null; // attribute not available
        }

        return attribute.getNodeValue();
    }

    /************************************************************************************
     * get sample precision from {@link Node}
     * 
     * @param domNode given {@link Node}
     ************************************************************************************/
    private String getSamplePrecision(Node domNode) {
        Node markerSequenceNode = getFirstElementByName(domNode, "markerSequence");
        if (markerSequenceNode == null) {
            return null; // markerSequence element not available
        }

        Node sofNode = getFirstElementByName(markerSequenceNode, "sof");
        if (sofNode == null) {
            return null; // sof element not available
        }

        Node attribute = getAttributeByName(sofNode, "samplePrecision");
        if (attribute == null) {
            return null; // attribute not available
        }

        return attribute.getNodeValue();
    }

    /************************************************************************************
     * get number of frame components from {@link Node}
     * 
     * @param domNode given {@link Node}
     ************************************************************************************/
    private String getNumFrames(Node domNode) {
        Node markerSequenceNode = getFirstElementByName(domNode, "markerSequence");
        if (markerSequenceNode == null) {
            return null; // markerSequence element not available
        }

        Node sofNode = getFirstElementByName(markerSequenceNode, "sof");
        if (sofNode == null) {
            return null; // sof element not available
        }

        Node attribute = getAttributeByName(sofNode, "numFrameComponents");
        if (attribute == null) {
            return null; // attribute not available
        }

        return attribute.getNodeValue();
    }

    /************************************************************************************
     * get Dom {@link Node} from parent {@link Node} with given name
     * 
     * @param inNode the parent {@link Node}
     * @param elementName the name of the Node to look for
     ************************************************************************************/
    private Node getFirstElementByName(Node inNode, String elementName) {
        NodeList list = inNode.getChildNodes();

        int i = 0;
        while (i < list.getLength()) {
            Node n = list.item(i);

            if ((n.getNodeType() == Node.ELEMENT_NODE) && (n.getNodeName().equals(elementName))) {
                return n;
            }

            i++;
        }
        return null;
    }

    /************************************************************************************
     * get Dom {@link Node} of attribute from parent {@link Node} with given name
     * 
     * @param inNode the parent {@link Node}
     * @param attributeName the name of the attribute to look for
     ************************************************************************************/
    private Node getAttributeByName(Node inNode, String attributeName) {
        NamedNodeMap nnm = inNode.getAttributes();
        return nnm.getNamedItem(attributeName);
    }

    @Override
    public void setWriterCompressionValue(int inWriterCompressionValue) throws ParameterNotSupportedException {
        if ((inWriterCompressionValue < 0) || (inWriterCompressionValue > 100)) {
            ParameterNotSupportedException pnse = new ParameterNotSupportedException(
                    "Value for JPEG compression must be between 0 and 100");
            throw pnse;
        }
        writerCompressionValue = inWriterCompressionValue;
    }

    /** Patches a JPEG file that is missing a JFIF marker **/
    private static class PatchInputStream extends FilterInputStream {
        private static final int[] JFIF = { 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x02, 0x00,
                0x00, 0x01, 0x00, 0x01, 0x00, 0x00 };
        int position = 0;

        public PatchInputStream(InputStream in) {
            super(in);
        }

        @Override
        public int read() throws IOException {
            int result;
            if (position < 2) {
                result = in.read();
            } else if (position < (2 + JFIF.length)) {
                result = JFIF[position - 2];
            } else {
                result = in.read();
            }
            position++;
            return result;
        }

        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            final int max = off + len;
            int bytesread = 0;
            for (int i = off; i < max; i++) {
                final int bi = read();
                if (bi == -1) {
                    if (bytesread == 0) {
                        bytesread = -1;
                    }
                    break;
                } else {
                    b[i] = (byte) bi;
                    bytesread++;
                }
            }
            return bytesread;
        }
    }

    /** Remove the bytes between the file init and the JFIF marker **/
    private static class RemoveHeaderInputStream extends FilterInputStream {

        // Header with the correct bytes (no more than needed)
        private static final int[] JFIF = { 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46 };

        int position = 0;
        int positionJFIF = 0;

        public RemoveHeaderInputStream(InputStream in) {
            super(in);
        }

        @Override
        public int read() throws IOException {
            int result;
            if (position < 2) {
                result = in.read();

                // Seek for the header until the end of file
            } else if (positionJFIF < JFIF.length) {

                // Advance positions until JFIF is found
                while ((result = in.read()) != JFIF[positionJFIF]) {
                    position++;
                }

                // Una vez
                positionJFIF++;

            } else {
                result = in.read();
            }
            position++;
            return result;
        }

        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            final int max = off + len;
            int bytesread = 0;
            for (int i = off; i < max; i++) {
                final int bi = read();
                if (bi == -1) {
                    if (bytesread == 0) {
                        bytesread = -1;
                    }
                    break;
                } else {
                    b[i] = (byte) bi;
                    bytesread++;
                }
            }
            return bytesread;
        }
    }

    private IIOImage createImage(InputStream istr, int attempt) throws ImageInterpreterException {
        ImageInputStream iis = null;
        Iterator<ImageReader> ri = null;
        ImageReadParam param = null;
        ImageReader ir = null;
        BufferedImage bi = null;
        // Raster raster = null;
        // int[] bufferTypes = new int[] { DataBuffer.TYPE_BYTE, DataBuffer.TYPE_INT, DataBuffer.TYPE_FLOAT, DataBuffer.TYPE_DOUBLE,
        // DataBuffer.TYPE_SHORT, DataBuffer.TYPE_USHORT, DataBuffer.TYPE_UNDEFINED };

        // Create raster from image reader
        try {
            iis = ImageIO.createImageInputStream(istr);
            ri = ImageIO.getImageReaders(iis);
            if (!ri.hasNext()) {
                // List<ImageReader> list = new ArrayList<ImageReader>();
                // list.add(new JPEGImageReader(new JPEGImageReaderSpi()));
                ri = IteratorUtils.getIterator(new JPEGImageReader(new JPEGImageReaderSpi()));
            }
        } catch (IOException e) {
            throw new ImageInterpreterException("Error reading input stream: " + e.toString());
        }
        while (ri.hasNext()) {
            ir = ri.next();
            try {
                ir.setInput(iis);
                param = ir.getDefaultReadParam();
                bi = ir.read(0, param);
                // raster = ir.readRaster(0, param);
                if (bi != null) {
                    break;
                }
            } catch (Error e) {
                LOGGER.error("Failed to render image with ImageReader: " + e.toString());
                continue;
            } catch (Exception e) {
                LOGGER.error("Failed to render image with ImageReader: " + e.toString());
                continue;
            }
        }

        // store metadata
        IIOMetadata md = null;
        try {
            md = getImageMetadata(ir);
        } catch (ImageInterpreterException e) {
            LOGGER.error("Failed to extract metadata from image: " + e.getMessage());
            InputStream patchedInputStream = null;
            if (attempt <= 1) {
                patchedInputStream = attempt == 0 ? new PatchInputStream(istr) : new RemoveHeaderInputStream(istr);
                if (istr != null) {
                    try {
                        istr.close();
                    } catch (IOException e1) {
                        LOGGER.error("Failed to close input stream");
                    }
                }
                return createImage(patchedInputStream, attempt + 1);
            } else {
                LOGGER.error("Unable to read image metadata.");
                md = null;
            }
        } finally {
            if (iis != null) {
                try {
                    iis.close();
                } catch (IOException e) {
                    LOGGER.error("Failed to close image stream", e);
                }
            }
        }

        // // create buffered image from raster
        // int count = 0;
        // while (count < bufferTypes.length) {
        // int bufferType = bufferTypes[count++];
        // try {
        // SampleModel sm = RasterFactory.createPixelInterleavedSampleModel(bufferType, raster.getWidth(), raster.getHeight(),
        // raster.getNumBands());
        // ColorModel cm = PlanarImage.createColorModel(sm);
        // // cm = bi.getColorModel();
        // ColorSpace sourceColorSpace = cm.getColorSpace();
        // ColorSpace destColorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB);
        // ColorConvertOp op = new ColorConvertOp(sourceColorSpace, destColorSpace, null);
        // // WritableRaster wraster = raster.createInterleavedRaster(bufferTypes[count-1], raster.getWidth(), raster.getHeight(),
        // raster.getNumBands(), null);
        // // WritableRaster wraster = op.createCompatibleDestRaster(raster);
        // WritableRaster wraster = op.filter(raster, null);
        // // Raster raster2 = bi.getRaster();
        // // cm = new ComponentColorModel(cs, false, false, ColorModel.OPAQUE, bufferType);
        // // sm = bi.getSampleModel();
        // // cm = bi.getColorModel();
        // // cm = ColorModel.getRGBdefault();
        // bi = new BufferedImage(cm, wraster, false, null);
        // cm.finalize();
        // } catch (Error e) {
        // LOGGER.debug("Failed to render image with BufferType " + bufferType);
        // continue;
        // } catch (Exception e) {
        // LOGGER.debug("Failed to render image with BufferType " + bufferType);
        // continue;
        // }
        // break;
        // }

        if (bi == null) {
            throw new ImageInterpreterException("Failed to extract buffered image from image reader");
        }
        IIOImage image = new IIOImage(bi, null, md);
        return image;
    }

    private IIOMetadata getImageMetadata(ImageReader ir) throws ImageInterpreterException {
        IIOMetadata md = null;
        Node treeNode = null;
        try {
            md = ir.getImageMetadata(0);
            String formatName = md.getNativeMetadataFormatName();
            treeNode = md.getAsTree(formatName);
        } catch (Error e) {
            throw new ImageInterpreterException("Error when attempting to parse image metadata: " + e.toString());
        } catch (Exception e) {
            throw new ImageInterpreterException("Error when attempting to parse image metadata: " + e.toString());
        }
        if ((treeNode == null) || (treeNode.getChildNodes() == null)) {
            throw new ImageInterpreterException("Image metadata Node is null or empty");
        }
        return md;
    }

    @Override
    public void createByteStreamFromRenderedImage() {
        // TODO Auto-generated method stub

    }
}