![JAR search and dependency download from the Maven repository](/logo.png)
com.viaoa.image.jpg.JFIFInputStream Maven / Gradle / Ivy
Show all versions of oa-core Show documentation
/*
* @(#)JFIFInputStream.java
*
* Copyright (c) 2008-2012 Werner Randelshofer, Immensee, Switzerland.
* All rights reserved.
*
* You may not use, copy or modify this file, except in compliance with the
* license agreement you entered into with Werner Randelshofer.
* For details see accompanying license terms.
*/
package com.viaoa.image.jpg;
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 Immensee
* @version $Id: JFIFInputStream.java 175 2012-03-09 14:32:55Z 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
*/
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
*/
public Segment getNextSegment() throws IOException {
// If we are inside of a marker segment, skip the
// marker
if (!segment.isEntropyCoded()) {
markerFound = false;
do {
long skipped = in.skip(segment.length - offset + segment.offset);
if (skipped == -1) {
segment = new Segment(0, offset, -1);
return null;
}
offset += skipped;
} while (offset < segment.length + segment.offset);
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 || length >= 0xffff) {
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. */
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;
}
}