123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626 |
- /* JPEGDecoder.java --
- Copyright (C) 2006 Free Software Foundation, Inc.
- This file is part of GNU Classpath.
- GNU Classpath is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2, or (at your option)
- any later version.
- GNU Classpath is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with GNU Classpath; see the file COPYING. If not, write to the
- Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301 USA.
- Linking this library statically or dynamically with other modules is
- making a combined work based on this library. Thus, the terms and
- conditions of the GNU General Public License cover the whole
- combination.
- As a special exception, the copyright holders of this library give you
- permission to link this library with independent modules to produce an
- executable, regardless of the license terms of these independent
- modules, and to copy and distribute the resulting executable under
- terms of your choice, provided that you also meet, for each linked
- independent module, the terms and conditions of the license of that
- module. An independent module is a module which is not derived from
- or based on this library. If you modify this library, you may extend
- this exception to your version of the library, but you are not
- obligated to do so. If you do not wish to do so, delete this
- exception statement from your version. */
- package gnu.javax.imageio.jpeg;
- import java.io.IOException;
- import java.nio.ByteOrder;
- import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
- import javax.imageio.plugins.jpeg.JPEGQTable;
- import javax.imageio.stream.ImageInputStream;
- import java.util.ArrayList;
- import java.util.Hashtable;
- import java.awt.Point;
- import java.awt.Transparency;
- import java.awt.color.ColorSpace;
- import java.awt.image.BufferedImage;
- import java.awt.image.ComponentColorModel;
- import java.awt.image.DataBuffer;
- import java.awt.image.Raster;
- import java.awt.image.WritableRaster;
- public class JPEGDecoder
- {
- byte majorVersion;
- byte minorVersion;
- byte units;
- short Xdensity;
- short Ydensity;
- byte Xthumbnail;
- byte Ythumbnail;
- byte[] thumbnail;
- BufferedImage image;
- int width;
- int height;
- byte marker;
- /**
- * This decoder expects JFIF 1.02 encoding.
- */
- public static final byte MAJOR_VERSION = (byte) 1;
- public static final byte MINOR_VERSION = (byte) 2;
- /**
- * The length of the JFIF field not including thumbnail data.
- */
- public static final short JFIF_FIXED_LENGTH = 16;
- /**
- * The length of the JFIF extension field not including extension
- * data.
- */
- public static final short JFXX_FIXED_LENGTH = 8;
- private JPEGImageInputStream jpegStream;
- ArrayList jpegFrames = new ArrayList();
- JPEGHuffmanTable[] dcTables = new JPEGHuffmanTable[4];
- JPEGHuffmanTable[] acTables = new JPEGHuffmanTable[4];
- JPEGQTable[] qTables = new JPEGQTable[4];
- public int getHeight()
- {
- return height;
- }
- public int getWidth()
- {
- return width;
- }
- public JPEGDecoder(ImageInputStream in)
- throws IOException, JPEGException
- {
- jpegStream = new JPEGImageInputStream(in);
- jpegStream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
- if (jpegStream.findNextMarker() != JPEGMarker.SOI)
- throw new JPEGException("Failed to find SOI marker.");
- if (jpegStream.findNextMarker() != JPEGMarker.APP0)
- throw new JPEGException("Failed to find APP0 marker.");
- int length = jpegStream.readShort();
- if (!(length >= JFIF_FIXED_LENGTH))
- throw new JPEGException("Failed to find JFIF field.");
- byte[] identifier = new byte[5];
- jpegStream.read(identifier);
- if (identifier[0] != JPEGMarker.JFIF_J
- || identifier[1] != JPEGMarker.JFIF_F
- || identifier[2] != JPEGMarker.JFIF_I
- || identifier[3] != JPEGMarker.JFIF_F
- || identifier[4] != JPEGMarker.X00)
- throw new JPEGException("Failed to read JFIF identifier.");
- majorVersion = jpegStream.readByte();
- minorVersion = jpegStream.readByte();
- if (majorVersion != MAJOR_VERSION
- || (majorVersion == MAJOR_VERSION
- && minorVersion < MINOR_VERSION))
- throw new JPEGException("Unsupported JFIF version.");
- units = jpegStream.readByte();
- if (units > (byte) 2)
- throw new JPEGException("Units field is out of range.");
- Xdensity = jpegStream.readShort();
- Ydensity = jpegStream.readShort();
- Xthumbnail = jpegStream.readByte();
- Ythumbnail = jpegStream.readByte();
- // 3 * for RGB data
- int thumbnailLength = 3 * Xthumbnail * Ythumbnail;
- if (length > JFIF_FIXED_LENGTH
- && thumbnailLength != length - JFIF_FIXED_LENGTH)
- throw new JPEGException("Invalid length, Xthumbnail"
- + " or Ythumbnail field.");
- if (thumbnailLength > 0)
- {
- thumbnail = new byte[thumbnailLength];
- if (jpegStream.read(thumbnail) != thumbnailLength)
- throw new IOException("Failed to read thumbnail.");
- }
- }
- public void decode()
- throws IOException
- {
- System.out.println ("DECODE!!!");
- // The frames in this jpeg are loaded into a list. There is
- // usually just one frame except in heirarchial progression where
- // there are multiple frames.
- JPEGFrame frame = null;
- // The restart interval defines how many MCU's we should have
- // between the 8-modulo restart marker. The restart markers allow
- // us to tell whether or not our decoding process is working
- // correctly, also if there is corruption in the image we can
- // recover with these restart intervals. (See RSTm DRI).
- int resetInterval = 0;
- // The JPEGDecoder constructor parses the JFIF field. At this
- // point jpegStream points to the first byte after the JFIF field.
- // Find the first marker after the JFIF field.
- byte marker = jpegStream.findNextMarker();
- // Check for a JFIF extension field directly following the JFIF
- // header and advance the current marker to the next marker in the
- // stream, if necessary.
- decodeJFIFExtension();
- // Loop through until there are no more markers to read in, at
- // that point everything is loaded into the jpegFrames array and
- // can be processed.
- while (true)
- {
- switch (marker)
- {
- // APPn Application Reserved Information - Just throw this
- // information away because we wont be using it.
- case JPEGMarker.APP0:
- case JPEGMarker.APP1:
- case JPEGMarker.APP2:
- case JPEGMarker.APP3:
- case JPEGMarker.APP4:
- case JPEGMarker.APP5:
- case JPEGMarker.APP6:
- case JPEGMarker.APP7:
- case JPEGMarker.APP8:
- case JPEGMarker.APP9:
- case JPEGMarker.APP10:
- case JPEGMarker.APP11:
- case JPEGMarker.APP12:
- case JPEGMarker.APP13:
- case JPEGMarker.APP14:
- case JPEGMarker.APP15:
- jpegStream.skipBytes(jpegStream.readShort() - 2);
- break;
- case JPEGMarker.SOF0:
- // SOFn Start of Frame Marker, Baseline DCT - This is the start
- // of the frame header that defines certain variables that will
- // be carried out through the rest of the encoding. Multiple
- // frames are used in a heirarchiel system, however most JPEG's
- // only contain a single frame.
- jpegFrames.add(new JPEGFrame());
- frame = (JPEGFrame) jpegFrames.get(jpegFrames.size() - 1);
- // Skip the frame length.
- jpegStream.readShort();
- // Bits percision, either 8 or 12.
- frame.setPrecision(jpegStream.readByte());
- // Scan lines = to the height of the frame.
- frame.setScanLines(jpegStream.readShort());
- // Scan samples per line = to the width of the frame.
- frame.setSamplesPerLine(jpegStream.readShort());
- // Number of Color Components (or channels).
- frame.setComponentCount(jpegStream.readByte());
- // Set the color mode for this frame, so far only 2 color
- // modes are supported.
- if (frame.getComponentCount() == 1)
- frame.setColorMode(JPEGFrame.JPEG_COLOR_GRAY);
- else
- frame.setColorMode(JPEGFrame.JPEG_COLOR_YCbCr);
- // Add all of the necessary components to the frame.
- for (int i = 0; i < frame.getComponentCount(); i++)
- frame.addComponent(jpegStream.readByte(), jpegStream.readByte(),
- jpegStream.readByte());
- break;
- case JPEGMarker.SOF2:
- jpegFrames.add(new JPEGFrame());
- frame = (JPEGFrame) jpegFrames.get(jpegFrames.size() - 1);
- // Skip the frame length.
- jpegStream.readShort();
- // Bits percision, either 8 or 12.
- frame.setPrecision(jpegStream.readByte());
- // Scan lines = to the height of the frame.
- frame.setScanLines(jpegStream.readShort());
- // Scan samples per line = to the width of the frame.
- frame.setSamplesPerLine(jpegStream.readShort());
- // Number of Color Components (or channels).
- frame.setComponentCount(jpegStream.readByte());
- // Set the color mode for this frame, so far only 2 color
- // modes are supported.
- if (frame.getComponentCount() == 1)
- frame.setColorMode(JPEGFrame.JPEG_COLOR_GRAY);
- else
- frame.setColorMode(JPEGFrame.JPEG_COLOR_YCbCr);
- // Add all of the necessary components to the frame.
- for (int i = 0; i < frame.getComponentCount(); i++)
- frame.addComponent(jpegStream.readByte(), jpegStream.readByte(),
- jpegStream.readByte());
- break;
- case JPEGMarker.DHT:
- // DHT non-SOF Marker - Huffman Table is required for decoding
- // the JPEG stream, when we receive a marker we load in first
- // the table length (16 bits), the table class (4 bits), table
- // identifier (4 bits), then we load in 16 bytes and each byte
- // represents the count of bytes to load in for each of the 16
- // bytes. We load this into an array to use later and move on 4
- // huffman tables can only be used in an image.
- int huffmanLength = (jpegStream.readShort() - 2);
- // Keep looping until we are out of length.
- int index = huffmanLength;
- // Multiple tables may be defined within a DHT marker. This
- // will keep reading until there are no tables left, most
- // of the time there are just one tables.
- while (index > 0)
- {
- // Read the identifier information and class
- // information about the Huffman table, then read the
- // 16 byte codelength in and read in the Huffman values
- // and put it into table info.
- byte huffmanInfo = jpegStream.readByte();
- byte tableClass = (byte) (huffmanInfo >> 4);
- byte huffmanIndex = (byte) (huffmanInfo & 0x0f);
- short[] codeLength = new short[16];
- jpegStream.readFully(codeLength, 0, codeLength.length);
- int huffmanValueLen = 0;
- for (int i = 0; i < 16; i++)
- huffmanValueLen += codeLength[i];
- index -= (huffmanValueLen + 17);
- short[] huffmanVal = new short[huffmanValueLen];
- for (int i = 0; i < huffmanVal.length; i++)
- huffmanVal[i] = jpegStream.readByte();
- // Assign DC Huffman Table.
- if (tableClass == HuffmanTable.JPEG_DC_TABLE)
- dcTables[(int) huffmanIndex] = new JPEGHuffmanTable(codeLength,
- huffmanVal);
- // Assign AC Huffman Table.
- else if (tableClass == HuffmanTable.JPEG_AC_TABLE)
- acTables[(int) huffmanIndex] = new JPEGHuffmanTable(codeLength,
- huffmanVal);
- }
- break;
- case JPEGMarker.DQT:
- // DQT non-SOF Marker - This defines the quantization
- // coeffecients, this allows us to figure out the quality of
- // compression and unencode the data. The data is loaded and
- // then stored in to an array.
- short quantizationLength = (short) (jpegStream.readShort() - 2);
- for (int j = 0; j < quantizationLength / 65; j++)
- {
- byte quantSpecs = jpegStream.readByte();
- int[] quantData = new int[64];
- if ((byte) (quantSpecs >> 4) == 0)
- // Precision 8 bit.
- {
- for (int i = 0; i < 64; i++)
- quantData[i] = jpegStream.readByte();
- }
- else if ((byte) (quantSpecs >> 4) == 1)
- // Precision 16 bit.
- {
- for (int i = 0; i < 64; i++)
- quantData[i] = jpegStream.readShort();
- }
- qTables[(int) (quantSpecs & 0x0f)] = new JPEGQTable (quantData);
- }
- break;
- case JPEGMarker.SOS:
- // SOS non-SOF Marker - Start Of Scan Marker, this is where the
- // actual data is stored in a interlaced or non-interlaced with
- // from 1-4 components of color data, if three components most
- // likely a YCrCb model, this is a fairly complex process.
- // Read in the scan length.
- jpegStream.readShort();
- // Number of components in the scan.
- byte numberOfComponents = jpegStream.readByte();
- byte[] componentSelector = new byte[numberOfComponents];
- for (int i = 0; i < numberOfComponents; i++)
- {
- // Component ID, packed byte containing the Id for the
- // AC table and DC table.
- byte componentID = jpegStream.readByte();
- byte tableInfo = jpegStream.readByte();
- frame.setHuffmanTables(componentID,
- acTables[(byte) (tableInfo >> 4)],
- dcTables[(byte) (tableInfo & 0x0f)]);
- componentSelector[i] = componentID;
- }
- byte startSpectralSelection = jpegStream.readByte();
- byte endSpectralSelection = jpegStream.readByte();
- byte successiveApproximation = jpegStream.readByte();
- int mcuIndex = 0;
- int mcuTotalIndex = 0;
- // This loops through until a MarkerTagFound exception is
- // found, if the marker tag is a RST (Restart Marker) it
- // simply skips it and moves on this system does not handle
- // corrupt data streams very well, it could be improved by
- // handling misplaced restart markers.
- while (true)
- {
- try
- {
- // Loop though capturing MCU, instruct each
- // component to read in its necessary count, for
- // scaling factors the components automatically
- // read in how much they need
- for (int compIndex = 0; compIndex < numberOfComponents; compIndex++)
- {
- JPEGComponent comp = frame.components.getComponentByID(componentSelector[compIndex]);
- comp.readComponentMCU(jpegStream);
- }
- mcuIndex++;
- mcuTotalIndex++;
- }
- // We've found a marker, see if the marker is a restart
- // marker or just the next marker in the stream. If
- // it's the next marker in the stream break out of the
- // while loop, if it's just a restart marker skip it
- catch (JPEGMarkerFoundException bse)
- {
- // Handle JPEG Restart Markers, this is where the
- // count of MCU's per interval is compared with
- // the count actually obtained, if it's short then
- // pad on some MCU's ONLY for components that are
- // greater than one. Also restart the DC prediction
- // to zero.
- if (marker == JPEGMarker.RST0
- || marker == JPEGMarker.RST1
- || marker == JPEGMarker.RST2
- || marker == JPEGMarker.RST3
- || marker == JPEGMarker.RST4
- || marker == JPEGMarker.RST5
- || marker == JPEGMarker.RST6
- || marker == JPEGMarker.RST7)
- {
- for (int compIndex = 0; compIndex < numberOfComponents; compIndex++)
- {
- JPEGComponent comp = frame.components.getComponentByID(componentSelector[compIndex]);
- if (compIndex > 1)
- comp.padMCU(mcuTotalIndex, resetInterval - mcuIndex);
- comp.resetInterval();
- }
- mcuTotalIndex += (resetInterval - mcuIndex);
- mcuIndex = 0;
- }
- else
- {
- // We're at the end of our scan, exit out.
- break;
- }
- }
- }
- break;
- case JPEGMarker.DRI:
- // DRI - This defines the restart interval, if we have a
- // restart interval when we reach our restart modulo calculate
- // whether the count of MCU's specified in the restart
- // interval have been reached, if they havent then pad with
- // whatever MCU was last used, this is supposed to be a form of
- // error recovery but it turns out that some JPEG encoders
- // purposely cause missing MCU's on repeating MCU's to compress
- // data even more (even though it adds an extra layer of
- // complexity.. But since when is JPEG easy?
- jpegStream.skipBytes(2);
- resetInterval = jpegStream.readShort();
- break;
- case JPEGMarker.COM:
- // COM - This is a comment that was inserted into the JPEG, we
- // simply skip over the comment because it's really of no
- // importance, usually contains a verbal description of the
- // application or author who created the JPEG.
- jpegStream.skipBytes(jpegStream.readShort() - 2);
- break;
- case JPEGMarker.DNL:
- // DNL - This sets the height of the image. This is the Define
- // Number Lines for the image, I'm not sure exactly why we need
- // this but, whatever we'll abide.
- frame.setScanLines(jpegStream.readShort());
- break;
- case JPEGMarker.EOI:
- // EOI - End of Image, this processes the frames and turns the
- // frames into a buffered image.
- if (jpegFrames.size() == 0)
- {
- return;
- }
- else if (jpegFrames.size() == 1)
- {
- // Only one frame, JPEG Non-Heirarchial Frame.
- DCT myDCT = new DCT();
- WritableRaster raster =
- Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
- frame.width,
- frame.height,
- frame.getComponentCount(),
- new Point(0, 0));
- // Unencode the data.
- for (int i = 0; i < frame.getComponentCount(); i++)
- {
- JPEGComponent comp = frame.components.get(i);
- comp.setQuantizationTable(qTables[comp.quant_id].getTable());
- comp.quantitizeData();
- comp.idctData(myDCT);
- }
- // Scale the image and write the data to the raster.
- for (int i = 0; i < frame.getComponentCount(); i++)
- {
- JPEGComponent comp = frame.components.get(i);
- comp.scaleByFactors();
- comp.writeData(raster, i);
- // Ensure garbage collection.
- comp = null;
- }
- // Grayscale Color Image (1 Component).
- if (frame.getComponentCount() == 1)
- {
- ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
- ComponentColorModel ccm =
- new ComponentColorModel(cs, false, false,
- Transparency.OPAQUE,
- DataBuffer.TYPE_BYTE);
- image = new BufferedImage(ccm, raster, false,
- new Hashtable());
- }
- // YCbCr Color Image (3 Components).
- else if (frame.getComponentCount() == 3)
- {
- ComponentColorModel ccm =
- new ComponentColorModel(new YCbCr_ColorSpace(), false,
- false, Transparency.OPAQUE,
- DataBuffer.TYPE_BYTE);
- image = new BufferedImage(ccm, raster, false,
- new Hashtable());
- }
- // Possibly CMYK or RGBA ?
- else
- {
- throw new JPEGException("Unsupported Color Mode: 4 "
- + "Component Color Mode found.");
- }
- height = frame.height;
- width = frame.width;
- }
- else
- {
- //JPEG Heirarchial Frame (progressive or baseline).
- throw new JPEGException("Unsupported Codec Type:"
- + " Hierarchial JPEG");
- }
- break;
- case JPEGMarker.SOF1:
- // ERROR - If we encounter any of the following marker codes
- // error out with a codec exception, progressive, heirarchial,
- // differential, arithmetic, lossless JPEG's are not supported.
- // This is where enhancements can be made for future versions.
- // Thankfully 99% of all JPEG's are baseline DCT.
- throw new JPEGException("Unsupported Codec Type: Extended "
- + "Sequential DCT JPEG's Not-Supported");
- //case JPEGMarker.SOF2:
- // throw new JPEGException("Unsupported Codec Type: Progressive DCT JPEG's Not-Supported");
- case JPEGMarker.SOF3:
- throw new JPEGException("Unsupported Codec Type:"
- + " Lossless (sequential)");
- case JPEGMarker.SOF5:
- throw new JPEGException("Unsupported Codec Type:"
- + " Differential sequential DCT");
- case JPEGMarker.SOF6:
- throw new JPEGException("Unsupported Codec Type:"
- + " Differential progressive DCT");
- case JPEGMarker.SOF7:
- throw new JPEGException("Unsupported Codec Type:"
- + " Differential lossless");
- case JPEGMarker.SOF9:
- case JPEGMarker.SOF10:
- case JPEGMarker.SOF11:
- case JPEGMarker.SOF13:
- case JPEGMarker.SOF14:
- case JPEGMarker.SOF15:
- throw new JPEGException("Unsupported Codec Type:"
- + " Arithmetic Coding Frame");
- default:
- // Unknown marker found, ignore it.
- }
- marker = jpegStream.findNextMarker();
- }
- }
- // If the current marker is APP0, tries to decode a JFIF extension
- // and advances the current marker to the next marker in the stream.
- private void decodeJFIFExtension() throws IOException
- {
- if (marker == JPEGMarker.APP0)
- {
- int length = jpegStream.readShort();
- if (length >= JFXX_FIXED_LENGTH)
- {
- byte[] identifier = new byte[5];
- jpegStream.read(identifier);
- if (identifier[0] != JPEGMarker.JFIF_J
- || identifier[1] != JPEGMarker.JFIF_F
- || identifier[2] != JPEGMarker.JFIF_X
- || identifier[3] != JPEGMarker.JFIF_X
- || identifier[4] != JPEGMarker.X00)
- // Not a JFXX field. Ignore it and continue.
- jpegStream.skipBytes(length - 7);
- else
- {
- byte extension_code = jpegStream.readByte();
- switch (extension_code)
- {
- case JPEGMarker.JFXX_JPEG:
- // FIXME: add support for JFIF Extension:
- // Thumbnail coded using JPEG.
- jpegStream.skipBytes(length - 8);
- case JPEGMarker.JFXX_ONE_BPP:
- // FIXME: add support for JFIF Extension:
- // Thumbnail stored using 1 byte/pixel.
- jpegStream.skipBytes(length - 8);
- case JPEGMarker.JFXX_THREE_BPP:
- // FIXME: add support for JFIF Extension:
- // Thumbnail stored using 3 bytes/pixel.
- jpegStream.skipBytes(length - 8);
- }
- }
- }
- else
- {
- // Unknown APP0 marker. Ignore it and continue.
- jpegStream.skipBytes(length - 2);
- }
- marker = jpegStream.findNextMarker();
- }
- }
- public BufferedImage getImage()
- {
- return image;
- }
- }
|