All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ru.sbtqa.monte.media.jpeg.JFIFInputStream Maven / Gradle / Ivy

There is a newer version: 1.1.0-JAVA7
Show newest version
/* @(#)JFIFInputStream.java
 * Copyright © 2008-2012 Werner Randelshofer, Switzerland.
 * You may only use this software in accordance with the license terms.
 */
package ru.sbtqa.monte.media.jpeg;

import java.io.*;
import java.util.*;

/**
 * JFIFInputStream.
 * 
 * This InputStream uses two special marker values which do not exist in the
 * JFIF stream:
 * 
 * -1: marks junk data at the beginning of the file.
 * 0: marks entropy encoded image data.
 * 
 * 
 * The junk data at the beginning of the file can be accessed by calling the
 * read-methods immediately after opening the stream. Call nextSegment()
 * immediately after opening the stream if you are not interested into this junk
 * data.
 * 
 * Junk data at the end of the file is delivered as part of the EOI_MARKER
 * segment. Finish reading after encountering the EOI_MARKER segment if you are
 * not interested in this junk data.
 *
 * 
 * References:
* JPEG File Interchange Format Version 1.02
* http://www.jpeg.org/public/jfif.pdf * * Pennebaker, W., Mitchell, J. (1993).
* JPEG Still Image Data Compression Standard.
* Chapmann & Hall, New York.
* ISBN 0-442-01272-1
* * * @author Werner Randelshofer, Hausmatt 10, CH-6405 Goldau * @version $Id: JFIFInputStream.java 364 2016-11-09 19:54:25Z werner $ */ public class JFIFInputStream extends FilterInputStream { /** * This hash set holds the Id's of markers which stand alone, respectively * do no have a data segment. */ private final HashSet standaloneMarkers = new HashSet(); /** * This hash set holds the Id's of markers which have a data segment * followed by a entropy-coded data segment. */ private final HashSet doubleSegMarkers = new HashSet(); /** * Represents a segment within a JFIF File. */ public static class Segment { /** * Holds the marker code. A marker is an unsigned short between 0xff01 * and 0xfffe. */ public final int marker; /** * Holds the offset of the first data byte to the beginning of the * stream. */ public final long offset; /** * If the marker starts a marker segment, holds the length of the data * in the data segment. If the marker starts a entropy-coded data * segment, holds the value -1. */ public final int length; public Segment(int marker, long offset, int length) { this.marker = marker; this.offset = offset; this.length = length; } public boolean isEntropyCoded() { return length == -1; } @Override public String toString() { return "Segment marker=0x" + Integer.toHexString(marker) + " offset=" + offset + "=0x" + Long.toHexString(offset); } } private Segment segment; /** * This variable is set to true, if a 0xff byte has been found in * entropy-code data. */ private boolean markerFound; private int marker = JUNK_MARKER; private long offset = 0; private boolean isStuffed0xff = false; /** * JUNK_MARKER Marker (for data which is not part of the JFIF stream. */ public final static int JUNK_MARKER = -1; /** * Start of image */ public final static int SOI_MARKER = 0xffd8; /** * End of image */ public final static int EOI_MARKER = 0xffd9; /** * Temporary private use in arithmetic coding */ public final static int TEM_MARKER = 0xff01; /** * Start of scan */ public final static int SOS_MARKER = 0xffda; /** * APP1_MARKER Reserved for application use */ public final static int APP1_MARKER = 0xffe1; /** * APP2_MARKER Reserved for application use */ public final static int APP2_MARKER = 0xffe2; /** * Reserved for JPEG extensions */ public final static int JPG0_MARKER = 0xfff0; public final static int JPG1_MARKER = 0xfff1; public final static int JPG2_MARKER = 0xfff2; public final static int JPG3_MARKER = 0xfff3; public final static int JPG4_MARKER = 0xfff4; public final static int JPG5_MARKER = 0xfff5; public final static int JPG6_MARKER = 0xfff6; public final static int JPG7_MARKER = 0xfff7; public final static int JPG8_MARKER = 0xfff8; public final static int JPG9_MARKER = 0xfff9; public final static int JPGA_MARKER = 0xfffA; public final static int JPGB_MARKER = 0xfffB; public final static int JPGC_MARKER = 0xfffC; public final static int JPGD_MARKER = 0xfffD; /** * Start of frame markers */ public final static int SOF0_MARKER = 0xffc0;//nondifferential Huffman-coding frames with baseline DCT. public final static int SOF1_MARKER = 0xffc1;//nondifferential Huffman-coding frames with extended sequential DCT. public final static int SOF2_MARKER = 0xffc2;//nondifferential Huffman-coding frames with progressive DCT. public final static int SOF3_MARKER = 0xffc3;//nondifferential Huffman-coding frames with lossless (sequential) data. //public final static int SOF4_MARKER = 0xffc4;// public final static int SOF5_MARKER = 0xffc5;//differential Huffman-coding frames with differential sequential DCT. public final static int SOF6_MARKER = 0xffc6;//differential Huffman-coding frames with differential progressive DCT. public final static int SOF7_MARKER = 0xffc7;//differential Huffman-coding frames with differential lossless data. //public final static int SOF8_MARKER = 0xffc8;// public final static int SOF9_MARKER = 0xffc9;//nondifferential Arithmetic-coding frames with extended sequential DCT. public final static int SOFA_MARKER = 0xffcA;//nondifferential Arithmetic-coding frames with progressive DCT. public final static int SOFB_MARKER = 0xffcB;//nondifferential Arithmetic-coding frames with lossless (sequential) data. //public final static int SOFC_MARKER = 0xffcC;// public final static int SOFD_MARKER = 0xffcD;//differential Arithmetic-coding frames with differential sequential DCT. public final static int SOFE_MARKER = 0xffcE;//differential Arithmetic-coding frames with differential progressive DCT. public final static int SOFF_MARKER = 0xffcF;//differential Arithmetic-coding frames with differential lossless DCT. // Restart markers public final static int RST0_MARKER = 0xffd0; public final static int RST1_MARKER = 0xffd1; public final static int RST2_MARKER = 0xffd2; public final static int RST3_MARKER = 0xffd3; public final static int RST4_MARKER = 0xffd4; public final static int RST5_MARKER = 0xffd5; public final static int RST6_MARKER = 0xffd6; public final static int RST7_MARKER = 0xffd7; public JFIFInputStream(File f) throws IOException { this(new BufferedInputStream(new FileInputStream(f))); } public JFIFInputStream(InputStream in) { super(in); for (int i = RST0_MARKER; i <= RST7_MARKER; i++) { standaloneMarkers.add(i); // RST(i) Restart interval termination } standaloneMarkers.add(SOI_MARKER); // SOI_MARKER Start of image standaloneMarkers.add(EOI_MARKER); // EOI_MARKER End of image standaloneMarkers.add(TEM_MARKER); // TEM_MARKER Temporary private use in arithmetic coding standaloneMarkers.add(JPG0_MARKER); // JPEG Extensions standaloneMarkers.add(JPG1_MARKER); standaloneMarkers.add(JPG2_MARKER); standaloneMarkers.add(JPG3_MARKER); standaloneMarkers.add(JPG4_MARKER); standaloneMarkers.add(JPG5_MARKER); standaloneMarkers.add(JPG6_MARKER); standaloneMarkers.add(JPG7_MARKER); standaloneMarkers.add(JPG8_MARKER); standaloneMarkers.add(JPG9_MARKER); standaloneMarkers.add(JPGA_MARKER); standaloneMarkers.add(JPGB_MARKER); standaloneMarkers.add(JPGC_MARKER); standaloneMarkers.add(JPGD_MARKER); standaloneMarkers.add(0xffff); // Illegal marker doubleSegMarkers.add(SOS_MARKER); // SOS_MARKER Start of Scan // Start with a dummy entropy-coded data segment. segment = new Segment(-1, 0, -1); } /** * Gets the current segment from the input stream. * * @return The current segment. Returns null, if we encountered the end of * the stream. * @throws java.io.IOException TODO */ public Segment getSegment() throws IOException { return segment; } /** * Gets the next segment from the input stream. * * @return The next segment. Returns null, if we encountered the end of the * stream. * @throws java.io.IOException TODO */ public Segment getNextSegment() throws IOException { // If we are inside of a marker segment, skip the // marker if (!segment.isEntropyCoded()) { markerFound = false; while (offset < segment.length + segment.offset) { long skipped = in.skip(segment.length + segment.offset - offset); if (skipped <= 0) { // => EOF segment = new Segment(0, offset, -1); return null; } offset += skipped; } if (doubleSegMarkers.contains(segment.marker)) { segment = new Segment(0, offset, -1); return segment; } } // Scan the input stream for the next marker. while (!markerFound) { while (true) { int b; if (isStuffed0xff) { b = 0xff; isStuffed0xff = false; } else { b = read0(); } if (b == -1) { return null; } if (b == 0xff) { markerFound = true; break; } } int b = read0(); if (b == -1) { return null; } if (b == 0x00) { markerFound = false; } else if (b == 0xff) { isStuffed0xff = true; markerFound = false; } else { marker = 0xff00 | b; } } markerFound = false; /* if (marker <= 0xff00 || marker >= 0xffff) { throw new IOException("JFIFInputStream found illegal marker " + Integer.toHexString(marker) + " at offset " + offset + " 0x"+Long.toHexString(offset)+"."); }*/ // Note: 0xffff is an illegal marker segment, we process it here // for robustness. if (standaloneMarkers.contains(marker)) { segment = new Segment(0xff00 | marker, offset, -1); } else { int length = (read0() << 8) | read0(); if (length < 2) { throw new IOException("JFIFInputStream found illegal segment length " + length + " after marker " + Integer.toHexString(marker) + " at offset " + offset + "."); } segment = new Segment(0xff00 | marker, offset, length - 2); } return segment; } public long getStreamPosition() { return offset; } private int read0() throws IOException { int b = in.read(); if (b != -1) { offset++; } return b; } /** * Reads the next byte of data from this input stream. The value byte is * returned as an int in the range 0 to * 255. If no byte is available because the end of the stream * has been reached, the value -1 is returned. This method * blocks until input data is available, the end of the stream is detected, * or an exception is thrown. * * This method simply performs in.read() and returns the * result. * * @return the next byte of data, or -1 if the end of the * stream is reached. * @exception IOException if an I/O error occurs. * @see java.io.FilterInputStream#in */ @Override public int read() throws IOException { if (markerFound) { return -1; } int b; if (isStuffed0xff) { isStuffed0xff = false; b = 0xff; } else { b = read0(); } if (segment.isEntropyCoded()) { if (b == 0xff) { b = read0(); if (b == 0x00) { // found a stuffed 0xff byte return 0xff; } else if (b == 0xff) { // found an invalid sequence of two 0xff bytes isStuffed0xff = true; return 0xff; } markerFound = true; marker = 0xff00 | b; return -1; } } return b; } /** * Reads up to len b of data from this input stream into an * array of b. This method blocks until some input is available. * * This method simply performs in.read(b, off, len) and returns * the result. * * @param b the buffer into which the data is read. * @param off the start offset of the data. * @param len the maximum number of b read. * @return the total number of b read into the buffer, or -1 if * there is no more data because the end of the stream has been reached. * @exception IOException if an I/O error occurs. * @see java.io.FilterInputStream#in */ @Override public int read(byte b[], int off, int len) throws IOException { if (markerFound) { return -1; } int count = 0; if (segment.isEntropyCoded()) { for (; count < len; count++) { int data = read(); if (data == -1) { if (count == 0) { return -1; } break; } b[off + count] = (byte) data; } } else { long available = segment.length - offset + segment.offset; if (available <= 0) { return -1; } if (available < len) { len = (int) available; } count = in.read(b, off, len); if (count != -1) { offset += count; } } return count; } /** * Fully skips the specified number of bytes. * * @param n TODO * @throws java.io.IOException TODO */ public final void skipFully(long n) throws IOException { long total = 0; long cur = 0; while ((total < n) && ((cur = (int) in.skip(n - total)) > 0)) { total += cur; } offset += total; if (total < n) { throw new EOFException(); } } /** * Skips over and discards n b of data from the input stream. * The skip method may, for a variety of reasons, end up * skipping over some smaller number of b, possibly 0. The * actual number of b skipped is returned. * * This method simply performs in.skip(n). * * @param n the number of b to be skipped. * @return the actual number of b skipped. * @exception IOException if an I/O error occurs. */ @Override public long skip(long n) throws IOException { if (markerFound) { return -1; } long count = 0; if (segment.isEntropyCoded()) { for (; count < n; count++) { int data = read(); if (data == -1) { break; } } } else { long available = segment.length - offset + segment.offset; if (available < n) { n = (int) available; } count = in.skip(n); if (count != -1) { offset += count; } } return count; } /** * Marks the current position in this input stream. A subsequent call to the * reset method repositions this stream at the last marked * position so that subsequent reads re-read the same b. * * The readlimit argument tells this input stream to allow that * many b to be read before the mark position gets invalidated. * * This method simply performs in.mark(readlimit). * * @param readlimit the maximum limit of b that can be read before the mark * position becomes invalid. * @see java.io.FilterInputStream#in * @see java.io.FilterInputStream#reset() */ @Override public synchronized void mark(int readlimit) { // do nothing, since we don't support marking } /** * Repositions this stream to the position at the time the mark * method was last called on this input stream. * * This method simply performs in.reset(). * * Stream marks are intended to be used in situations where you need to read * ahead a little to see what's in the stream. Often this is most easily * done by invoking some general parser. If the stream is of the type * handled by the parse, it just chugs along happily. If the stream is not * of that type, the parser should toss an exception when it fails. If this * happens within readlimit b, it allows the outer code to reset the stream * and try another parser. * * @exception IOException if the stream has not been marked or if the mark * has been invalidated. * @see java.io.FilterInputStream#in * @see java.io.FilterInputStream#mark(int) */ @Override public synchronized void reset() throws IOException { throw new IOException("Reset not supported"); } /** * Tests if this input stream supports the mark and * reset methods. This method simply performs * in.markSupported(). * * @return true if this stream type supports the * mark and reset method; false * otherwise. * @see java.io.FilterInputStream#in * @see java.io.InputStream#mark(int) * @see java.io.InputStream#reset() */ @Override public boolean markSupported() { return false; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy