/*
* JPGDecoder.java
* Transform
*
* Copyright (c) 2009-2010 Flagstone Software Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Flagstone Software Ltd. nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.flagstone.transform.util.image;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.zip.DataFormatException;
import com.flagstone.transform.coder.BigDecoder;
import com.flagstone.transform.coder.Coder;
import com.flagstone.transform.image.DefineJPEGImage2;
import com.flagstone.transform.image.ImageTag;
import com.flagstone.transform.image.JPEGInfo;
/**
* JPGDecoder decodes JPEG images so they can be used in a Flash file.
*/
public final class JPGDecoder implements ImageProvider, ImageDecoder {
/** Message used to signal that the image cannot be decoded. */
private static final String BAD_FORMAT = "Unsupported format";
/** The width of the image in pixels. */
private transient int width;
/** The height of the image in pixels. */
private transient int height;
/** The image data. */
private transient byte[] image = new byte[0];
/** {@inheritDoc} */
@Override
public void read(final File file) throws IOException, DataFormatException {
read(new FileInputStream(file));
}
/** {@inheritDoc} */
@Override
public void read(final URL url) throws IOException, DataFormatException {
final URLConnection connection = url.openConnection();
if (!connection.getContentType().equals("image/bmp")) {
throw new DataFormatException(BAD_FORMAT);
}
final int length = connection.getContentLength();
if (length < 0) {
throw new FileNotFoundException(url.getFile());
}
read(url.openStream());
}
/** {@inheritDoc} */
@Override
public ImageTag defineImage(final int identifier) {
return new DefineJPEGImage2(identifier, image);
}
/** {@inheritDoc} */
@Override
public ImageDecoder newDecoder() {
return new JPGDecoder();
}
/** {@inheritDoc} */
@Override
public void read(final InputStream stream)
throws DataFormatException, IOException {
final BigDecoder coder = new BigDecoder(stream);
int marker;
int length;
do {
marker = coder.readUnsignedShort();
switch (marker) {
case JPEGInfo.SOI:
case JPEGInfo.EOI:
copyTag(marker, 0, coder);
break;
case JPEGInfo.SOF0:
case JPEGInfo.SOF2:
case JPEGInfo.DHT:
case JPEGInfo.DQT:
case JPEGInfo.COM:
length = coder.readUnsignedShort();
copyTag(marker, length, coder);
break;
case JPEGInfo.SOS:
length = coder.readUnsignedShort();
copyTag(marker, length, coder);
readEntropyData(coder);
break;
case JPEGInfo.DRI:
copyTag(marker, 0, coder);
readEntropyData(coder);
break;
default:
if ((marker & JPEGInfo.APP) == JPEGInfo.APP) {
length = coder.readUnsignedShort();
copyTag(marker, length, coder);
} else {
copyTag(marker, 0, coder);
}
break;
}
} while (marker != JPEGInfo.EOI);
final JPEGInfo info = new JPEGInfo();
info.decode(image);
width = info.getWidth();
height = info.getHeight();
}
/**
* Copy a JPEG tag.
* @param marker the identifier for the tag.
* @param length the length of the tag data.
* @param coder the decoder containing the JPEG image data.
* @throws IOException if there is an error reading the image data.
*/
private void copyTag(final int marker, final int length,
final BigDecoder coder) throws IOException {
byte[] bytes;
if (length > 0) {
bytes = new byte[length + 2];
} else {
bytes = new byte[2];
}
bytes[0] = (byte) (marker >> Coder.TO_LOWER_BYTE);
bytes[1] = (byte) marker;
if (length > 0) {
bytes[2] = (byte) (length >> Coder.TO_LOWER_BYTE);
bytes[3] = (byte) length;
coder.readBytes(bytes, 4, length - 2);
}
final int imgLength = image.length;
image = Arrays.copyOf(image, imgLength + bytes.length);
System.arraycopy(bytes, 0, image, imgLength, bytes.length);
}
/**
* Read the encoded image data from a JPEG image.
* @param coder the decoder containing the JPEG image data.
* @throws IOException if there is an error reading the image data.
*/
private void readEntropyData(final BigDecoder coder) throws IOException {
byte[] bytes = new byte[2048];
int index = 0;
int current;
int next;
do {
coder.mark();
current = coder.readByte();
if (current == 255) {
next = coder.readByte();
if (next == 0) {
if (index + 2 >= bytes.length) {
final int imgLength = image.length;
image = Arrays.copyOf(image, imgLength + index);
System.arraycopy(bytes, 0, image, imgLength, index);
index = 0;
}
bytes[index++] = (byte) current;
bytes[index++] = (byte) next;
} else {
if (index > 0) {
final int imgLength = image.length;
image = Arrays.copyOf(image, imgLength + index);
System.arraycopy(bytes, 0, image, imgLength, index);
index = 0;
}
coder.reset();
break;
}
} else {
if (index >= bytes.length) {
final int imgLength = image.length;
image = Arrays.copyOf(image, imgLength + bytes.length);
System.arraycopy(bytes, 0, image, imgLength, bytes.length);
index = 0;
}
bytes[index++] = (byte) current;
}
coder.unmark();
} while (true);
}
/** {@inheritDoc} */
@Override
public int getWidth() {
return width;
}
/** {@inheritDoc} */
@Override
public int getHeight() {
return height;
}
/** {@inheritDoc} */
@Override
public byte[] getImage() {
return Arrays.copyOf(image, image.length);
}
}