001: // JpegCOmmentWriter.java
002: // $Id: JpegCommentWriter.java,v 1.5 2000/10/19 15:53:26 ylafon Exp $
003: // (c) COPYRIGHT MIT, INRIA and Keio, 1999.
004: // Please first read the full copyright statement in file COPYRIGHT.html
005:
006: package org.w3c.tools.jpeg;
007:
008: import java.io.ByteArrayOutputStream;
009: import java.io.File;
010: import java.io.FileInputStream;
011: import java.io.IOException;
012: import java.io.InputStream;
013: import java.io.OutputStream;
014: import java.io.OutputStreamWriter;
015: import java.io.UnsupportedEncodingException;
016: import java.io.Writer;
017:
018: /**
019: * Allow you to write text comments to jpeg stream
020: * Some code has been adapted from wrjpgcom.c from The Independent JPEG Group
021: */
022: public class JpegCommentWriter extends Writer {
023:
024: // usual debug stuff
025: private static final boolean debug = true;
026:
027: InputStream inJpegData;
028: OutputStream outJpegData;
029: ByteArrayOutputStream byteStream;
030: OutputStreamWriter osw;
031: boolean init;
032: int lastMarker;
033:
034: /**
035: * Create a JpegCommentWriter, using an Input stream as the jpeg binary
036: * source, and writing in the output stream
037: * @param out, the output stream where the image will be written
038: * @param in, the input stream of the jpeg file, it MUST point to the
039: * beginning of the jpeg to avoid problems
040: */
041: public JpegCommentWriter(OutputStream out, InputStream in) {
042: super (out);
043: inJpegData = in;
044: outJpegData = out;
045: byteStream = new ByteArrayOutputStream(65536);
046: osw = new OutputStreamWriter(byteStream);
047: init = false;
048: }
049:
050: /**
051: * Create a JpegCommentWriter, using an Input stream as the jpeg binary
052: * source, and writing in the output stream
053: * @param out, the output stream where the image will be written
054: * @param in, the input stream of the jpeg file, it MUST point to the
055: * beginning of the jpeg to avoid problems
056: * @param enc, the encoding name used when you write comments
057: */
058: public JpegCommentWriter(OutputStream out, InputStream in,
059: String enc) throws UnsupportedEncodingException {
060: super (out);
061: inJpegData = in;
062: outJpegData = out;
063: byteStream = new ByteArrayOutputStream(65536);
064: osw = new OutputStreamWriter(byteStream, enc);
065: init = false;
066: }
067:
068: /**
069: * gets the encoding used by the comment writer
070: */
071: public String getEncoding() {
072: return osw.getEncoding();
073: }
074:
075: /**
076: * write one character
077: */
078: public void write(int ch) throws IOException {
079: osw.write(ch);
080: }
081:
082: /**
083: * write an array of characters
084: */
085: public void write(char[] buffer) throws IOException {
086: osw.write(buffer);
087: }
088:
089: /**
090: * write a portion of an array of characters
091: */
092: public void write(char[] buffer, int off, int len)
093: throws IOException {
094: osw.write(buffer, off, len);
095: }
096:
097: /**
098: * Write a String
099: */
100: public void write(String s) throws IOException {
101: osw.write(s);
102: }
103:
104: /**
105: * Write a portion of a String
106: */
107: public void write(String s, int off, int len) throws IOException {
108: osw.write(s, off, len);
109: }
110:
111: public void flush() throws IOException {
112: if (!init) {
113: dupFirstHeaders();
114: init = true;
115: }
116: osw.flush();
117: byte[] buffer;
118: synchronized (byteStream) {
119: buffer = byteStream.toByteArray();
120: byteStream.reset();
121: }
122: int buflen = buffer.length;
123: int curlen;
124: int curpos = 0;
125: // write the comments, using chunks if necessary
126: while (buflen > 0) {
127: writeMarker(Jpeg.M_COM);
128: curlen = Math.min(Jpeg.M_MAX_COM_LENGTH, buflen);
129: writeMarkerLength(curlen + 2);
130: outJpegData.write(buffer, curpos, curlen);
131: curpos += curlen;
132: buflen -= curlen;
133: }
134: ;
135: }
136:
137: public void close() throws IOException {
138: flush();
139: byte[] buf = new byte[1024];
140: int got;
141: // first dump the last marker
142: writeMarker(lastMarker);
143: // then the end of the file
144: do {
145: got = inJpegData.read(buf);
146: if (got < 0)
147: break;
148: outJpegData.write(buf, 0, got);
149: } while (got >= 0);
150: }
151:
152: /**
153: * copy the marker and return it
154: */
155: protected int dupFirstMarker() throws IOException, JpegException {
156: int c1, c2;
157: c1 = inJpegData.read();
158: c2 = inJpegData.read();
159: if (c1 != 0xFF || c2 != Jpeg.M_SOI)
160: throw new JpegException("Not a JPEG file");
161: outJpegData.write(c1);
162: outJpegData.write(c2);
163: return c2;
164: }
165:
166: /**
167: * read 2 bytes and create an integer out of it
168: */
169: protected int read2bytes() throws IOException, JpegException {
170: int c1, c2;
171: c1 = inJpegData.read();
172: if (c1 == -1)
173: throw new JpegException("Premature EOF in JPEG file");
174: c2 = inJpegData.read();
175: if (c2 == -1)
176: throw new JpegException("Premature EOF in JPEG file");
177: return (((int) c1) << 8) + ((int) c2);
178: }
179:
180: /**
181: * get the next marker, and eat extra bytes
182: */
183: protected int nextMarker() throws IOException {
184: int discarded_bytes = 0;
185: int c;
186:
187: /* Find 0xFF byte; count and skip any non-FFs. */
188: c = inJpegData.read();
189: while (c != 0xFF)
190: c = inJpegData.read();
191:
192: /* Get marker code byte, swallowing any duplicate FF bytes. Extra FFs
193: * are legal as pad bytes, so don't count them in discarded_bytes.
194: */
195: do {
196: c = inJpegData.read();
197: } while (c == 0xFF);
198:
199: return c;
200: }
201:
202: /**
203: * skip the body after a marker
204: */
205: protected void skipVariable() throws IOException, JpegException {
206: long len = (long) read2bytes() - 2;
207: if (len < 0)
208: throw new JpegException("Erroneous JPEG marker length");
209: while (len > 0) {
210: long saved = inJpegData.skip(len);
211: if (saved < 0)
212: throw new IOException("Error while reading jpeg stream");
213: len -= saved;
214: }
215: }
216:
217: /**
218: * dup the marker and the body
219: */
220: protected void dupHeader(int marker) throws IOException,
221: JpegException {
222: int len = read2bytes();
223: if (len < 2)
224: throw new JpegException("Erroneous JPEG marker length");
225: // dump the marker
226: writeMarker(marker);
227: // write the length
228: writeMarkerLength(len);
229: // and now copy the bytes
230: byte[] buf = new byte[1024];
231: int got;
232: len -= 2;
233: while (len > 0) {
234: got = inJpegData.read(buf, 0, Math.min(buf.length, len));
235: if (got < 0)
236: throw new IOException(
237: "Error while reading jpeg stream (EOF)");
238: outJpegData.write(buf, 0, got);
239: len -= got;
240: }
241: }
242:
243: protected void writeMarker(int marker) throws IOException {
244: outJpegData.write(0xFF);
245: outJpegData.write(marker);
246: }
247:
248: protected void writeMarkerLength(int len) throws IOException {
249: outJpegData.write((len >> 8) & 0xFF);
250: outJpegData.write(len & 0xFF);
251: }
252:
253: /**
254: * the the first headers until a SOF parker is found
255: */
256: protected void dupFirstHeaders() throws IOException {
257: int marker;
258: // first duplicate the header and make sure it is valid
259: try {
260: dupFirstMarker();
261: } catch (JpegException ex) {
262: if (debug) {
263: ex.printStackTrace();
264: }
265: throw new IOException(ex.getMessage());
266: }
267: // now dump the headers
268:
269: while (true) {
270: marker = nextMarker();
271: switch (marker) {
272: case Jpeg.M_SOF0: /* Baseline */
273: case Jpeg.M_SOF1: /* Extended sequential, Huffman */
274: case Jpeg.M_SOF2: /* Progressive, Huffman */
275: case Jpeg.M_SOF3: /* Lossless, Huffman */
276: case Jpeg.M_SOF5: /* Differential sequential, Huffman */
277: case Jpeg.M_SOF6: /* Differential progressive, Huffman */
278: case Jpeg.M_SOF7: /* Differential lossless, Huffman */
279: case Jpeg.M_SOF9: /* Extended sequential, arithmetic */
280: case Jpeg.M_SOF10: /* Progressive, arithmetic */
281: case Jpeg.M_SOF11: /* Lossless, arithmetic */
282: case Jpeg.M_SOF13: /* Differential sequential, arithmetic */
283: case Jpeg.M_SOF14: /* Differential progressive, arithmetic */
284: case Jpeg.M_SOF15: /* Differential lossless, arithmetic */
285: // bye!
286: lastMarker = marker;
287: return;
288: case Jpeg.M_SOS:
289: break;
290: case Jpeg.M_EOI:
291: lastMarker = marker;
292: return;
293: case Jpeg.M_APP12:
294: case Jpeg.M_COM: // remove previous comments
295: try {
296: skipVariable();
297: } catch (JpegException jex) {
298: if (debug)
299: jex.printStackTrace();
300: throw new IOException(jex.getMessage());
301: }
302: break;
303: default:
304: try {
305: dupHeader(marker);
306: } catch (JpegException jex) {
307: if (debug)
308: jex.printStackTrace();
309: throw new IOException(jex.getMessage());
310: }
311: break;
312: }
313: }
314: }
315:
316: /**
317: * The usual debugging tool
318: */
319: public static void main(String args[]) {
320: try {
321: File jpegFile = new File(args[0]);
322: JpegCommentWriter jcw = new JpegCommentWriter(System.out,
323: new FileInputStream(jpegFile));
324: jcw.write(args[1]);
325: jcw.close();
326: } catch (Exception ex) {
327: ex.printStackTrace();
328: }
329: }
330: }
|