
org.monte.media.jpeg.JFIFOutputStream Maven / Gradle / Ivy
/*
* @(#)JFIFOutputStream.java 1.0 2011-02-27
*
* Copyright (c) 2011 Werner Randelshofer, Goldau, 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 org.monte.media.jpeg;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteOrder;
import java.util.HashSet;
import java.util.Stack;
import javax.imageio.stream.FileImageOutputStream;
import javax.imageio.stream.ImageOutputStream;
/**
* {@code JFIFOutputStream}.
*
* This OutputStream supports writing of a JFIF stream.
*
*
* 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
* @version 1.0 2011-02-27 Created.
*/
public class JFIFOutputStream extends OutputStream {
/**
* 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();
/** 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;
private ImageOutputStream out;
private long streamOffset;
private Stack stack = new Stack();
public JFIFOutputStream(ImageOutputStream out) throws IOException {
this.out = out;
out.setByteOrder(ByteOrder.BIG_ENDIAN);
streamOffset = out.getStreamPosition();
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);
// JFIFInputStream returns segments with marker 0 for the data
// which follows the SOS segment.
standaloneMarkers.add(0);
doubleSegMarkers.add(SOS_MARKER); // SOS_MARKER Start of Scan
}
public JFIFOutputStream(File imgFile) throws IOException {
this(new FileImageOutputStream(imgFile));
}
/** Gets the position relative to the beginning of the IFF output stream.
*
* Usually this value is equal to the stream position of the underlying
* ImageOutputStream, but can be larger if the underlying stream already
* contained data.
*
* @return The relative stream position.
* @throws java.io.IOException
*/
public long getStreamPosition() throws IOException {
return out.getStreamPosition() - streamOffset;
}
/** Seeks relative to the beginning of the IFF output stream.
*
* Usually this equal to seeking in the underlying ImageOutputStream, but
* can be different if the underlying stream already contained data.
*
*/
public void seek(long newPosition) throws IOException {
out.seek(newPosition + streamOffset);
}
public void pushSegment(int marker) throws IOException {
stack.push(new Segment(marker));
}
public void popSegment() throws IOException {
Segment seg = stack.pop();
seg.finish();
}
/** Returns the offset of the current segment or -1 if none has been pushed. */
public long getSegmentOffset() throws IOException {
if (stack.peek() == null) {
return -1;
} else {
return stack.peek().offset;
}
}
/** Returns the length of the current segment or -1 if none has been pushed. */
public long getSegmentLength() throws IOException {
if (stack.peek() == null) {
return -1;
} else {
return getStreamPosition() - stack.peek().offset - 2;
}
}
public void finish() throws IOException {
while (!stack.empty()) {
popSegment();
}
}
@Override
public void close() throws IOException {
try {
finish();
} finally {
out.close();
}
}
/** Writes stuffed or non-stuffed bytes to the underlying output stream.
* Bytes are stuffed, if the stream is not currently in a segment.
*/
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (stack.size() == 0 || standaloneMarkers.contains(stack.peek().marker)) {
writeStuffed(b, off, len);
} else {
writeNonstuffed(b, off, len);
}
}
/** Writes a stuffed or non-stuffed byte to the underlying output stream.
* Bytes are stuffed, if the stream is not currently in a segment.
*/
@Override
public void write(int b) throws IOException {
if (stack.size() == 0 || standaloneMarkers.contains(stack.peek().marker)) {
writeStuffed(b);
} else {
writeNonstuffed(b);
}
}
/** Writes non-stuffed bytes to the underlying output stream.
*/
private void writeNonstuffed(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
}
/** Writes non-stuffed byte to the underlying output stream.
* Bytes should be stuffed, if the stream is not currently in a segment.
*/
private void writeNonstuffed(int b) throws IOException {
out.write(b);
}
/** Writes stuffed bytes to the underlying output stream.
*/
private void writeStuffed(byte[] b, int off, int len) throws IOException {
int n = off + len;
for (int i = off; i < n; i++) {
if (b[i] == -1) {
out.write(b, off, i - off + 1);
out.write(0);
off = i + 1;
}
}
if (n - off > 0) {
out.write(b, off, n - off);
}
}
/** Writes stuffed byte to the underlying output stream.
* Bytes should be stuffed, if the stream is not currently in a segment.
*/
private void writeStuffed(int b) throws IOException {
out.write(0xff);
if (b == 0xff) {
out.write(0);
}
}
/**
* Segment base class.
*/
private class Segment {
/**
* The marker of the segment.
*/
protected int marker;
/**
* The offset of the segment relative to the start of the
* ImageOutputStream.
*/
protected long offset;
protected boolean finished;
/**
* Creates a new Chunk at the current position of the ImageOutputStream.
* @param chunkType The chunkType of the chunk. A string with a length of 4 characters.
*/
public Segment(int marker) throws IOException {
this.marker = marker;
if (marker != 0) {
out.writeShort(marker);
offset = getStreamPosition();
if (!standaloneMarkers.contains(marker)) {
out.writeShort(0); // make room for the size field
}
}
}
/**
* Writes the segment to the ImageOutputStream and disposes it.
*/
public void finish() throws IOException {
if (!finished) {
if (!standaloneMarkers.contains(marker)) {
long size = getStreamPosition() - offset;
if (size > 0xffffL) {
throw new IOException("Segment \"" + marker + "\" is too large: " + size);
}
long pointer = getStreamPosition();
seek(offset);
out.writeShort((short) size);
seek(pointer);
}
finished = true;
}
}
}
}