/* * $Id$ * * Copyright (c) 2013 by Joel Uckelman * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License (LGPL) as published by the Free Software Foundation. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, copies are available * at http://www.opensource.org. */ package VASSAL.tools.image; import java.io.DataInputStream; import java.io.FileInputStream; import java.io.IOException; import VASSAL.tools.io.IOUtils; /** * A (partial) JPEG decoder. * * @author Joel Uckelman * @since 3.2.3 */ class JPEGDecoder { protected JPEGDecoder() {} static final int TEM = 0xFF01; static final int SOF0 = 0xFFC0; static final int SOF1 = 0xFFC1; static final int SOF2 = 0xFFC2; static final int SOF3 = 0xFFC3; static final int SOF4 = 0xFFC4; static final int SOF5 = 0xFFC5; static final int SOF6 = 0xFFC6; static final int SOF7 = 0xFFC7; static final int SOF8 = 0xFFC8; static final int SOF9 = 0xFFC9; static final int SOF10 = 0xFFCA; static final int SOF11 = 0xFFCB; static final int SOF12 = 0xFFCC; static final int SOF13 = 0xFFCD; static final int SOF14 = 0xFFCE; static final int SOF15 = 0xFFCF; static final int RST0 = 0xFFD0; static final int RST1 = 0xFFD1; static final int RST2 = 0xFFD2; static final int RST3 = 0xFFD3; static final int RST4 = 0xFFD4; static final int RST5 = 0xFFD5; static final int RST6 = 0xFFD6; static final int RST7 = 0xFFD7; static final int SOI = 0xFFD8; static final int EOI = 0xFFD9; static final int SOS = 0xFFDA; static final int APP0 = 0xFFE0; static final int APP2 = 0xFFE2; static final int APP13 = 0xFFED; static final int APP14 = 0xFFEE; public static boolean decodeSignature(DataInputStream in) throws IOException { return in.readUnsignedShort() == SOI; } public static Chunk decodeChunk(DataInputStream in) throws IOException { final int type = in.readUnsignedShort(); // marker type if ((type & 0xFF00) != 0xFF00) { throw new IOException("initial byte of chunk must be FF"); } byte[] data; if (type == TEM || (RST0 <= type && type <= EOI)) { // These chunks have no data data = new byte[0]; } else { final int length = in.readUnsignedShort(); data = new byte[length-2]; // length is inclusive of its own 2 bytes in.readFully(data); } // NB: This will blow up after reaching an SOS, due to it being follwed // by raw data instead of another chunk. If we want to find the next // chunk after an SOS, we have to scan for FF xx, where xx != 00. return new Chunk(type, data); } public static class Chunk { public final int type; public final byte[] data; private Chunk(int type, byte[] data) { this.type = type; this.data = data; } } public static void main(String[] args) throws IOException { DataInputStream in = null; try { in = new DataInputStream(new FileInputStream(args[0])); if (!JPEGDecoder.decodeSignature(in)) { System.out.println("Not a JPEG"); } JPEGDecoder.Chunk ch; do { ch = JPEGDecoder.decodeChunk(in); System.out.println("type == " + Integer.toHexString(ch.type) + ", length == " + ch.data.length); } while (ch.type != JPEGDecoder.EOI); } finally { IOUtils.closeQuietly(in); } } }