//
// JPEGTileDecoder.java
//
/*
OME Bio-Formats package for reading and converting biological file formats.
Copyright (C) 2005-@year@ UW-Madison LOCI and Glencoe Software, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package loci.formats.codec;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.ColorModel;
import java.awt.image.ImageConsumer;
import java.awt.image.ImageProducer;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
import loci.common.ByteArrayHandle;
import loci.common.RandomAccessInputStream;
import loci.common.Region;
import loci.formats.FormatException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <dl><dt><b>Source code:</b></dt>
* <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/bio-formats/src/loci/formats/codec/JPEGTileDecoder.java">Trac</a>,
* <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/codec/JPEGTileDecoder.java;hb=HEAD">Gitweb</a></dd></dl>
*
*/
public class JPEGTileDecoder {
// -- Constants --
protected static final Logger LOGGER =
LoggerFactory.getLogger(JPEGTileDecoder.class);
// -- Fields --
private TileConsumer consumer;
private TileCache tiles;
private RandomAccessInputStream in;
// -- JPEGTileDecoder API methods --
public void initialize(String id, int imageWidth) {
try {
initialize(new RandomAccessInputStream(id), imageWidth);
}
catch (IOException e) {
LOGGER.debug("", e);
}
}
public void initialize(RandomAccessInputStream in, int imageWidth) {
initialize(in, 0, 0, imageWidth);
}
public void initialize(RandomAccessInputStream in, int y, int h,
int imageWidth)
{
this.in = in;
tiles = new TileCache(y, h);
// pre-process the stream to make sure that the
// image width and height are non-zero
try {
long fp = in.getFilePointer();
boolean littleEndian = in.isLittleEndian();
in.order(false);
while (in.getFilePointer() < in.length() - 1) {
int code = in.readShort() & 0xffff;
int length = in.readShort() & 0xffff;
long pointer = in.getFilePointer();
if (length > 0xff00 || code < 0xff00) {
in.seek(pointer - 3);
continue;
}
if (code == 0xffc0) {
in.skipBytes(1);
int height = in.readShort() & 0xffff;
int width = in.readShort() & 0xffff;
if (height == 0 || width == 0) {
throw new RuntimeException(
"Width or height > 65500 is not supported.");
/*
if (height == 0) {
height = y + h;
}
if (width == 0) {
width = imageWidth;
}
long pos = in.getFilePointer() - fp - 4;
byte[] buf = new byte[(int) (in.length() - fp)];
in.seek(fp);
in.read(buf);
ByteArrayHandle handle = new ByteArrayHandle(buf);
handle.seek(pos);
handle.writeShort(height);
handle.writeShort(width);
this.in = new RandomAccessInputStream(handle);
*/
}
break;
}
else if (pointer + length - 2 < in.length()) {
in.seek(pointer + length - 2);
}
else {
break;
}
}
in.seek(fp);
in.order(littleEndian);
}
catch (IOException e) { }
try {
Toolkit toolkit = Toolkit.getDefaultToolkit();
byte[] data = new byte[this.in.available()];
this.in.readFully(data);
Image image = toolkit.createImage(data);
ImageProducer producer = image.getSource();
consumer = new TileConsumer(producer, y, h);
producer.startProduction(consumer);
while (producer.isConsumer(consumer));
}
catch (IOException e) { }
}
public byte[] getScanline(int y) {
try {
return tiles.get(0, y, consumer.getWidth(), 1);
}
catch (FormatException e) {
LOGGER.debug("", e);
}
catch (IOException e) {
LOGGER.debug("", e);
}
return null;
}
public int getWidth() {
return consumer.getWidth();
}
public int getHeight() {
return consumer.getHeight();
}
public void close() {
try {
if (in != null) {
in.close();
}
}
catch (IOException e) {
LOGGER.debug("", e);
}
tiles = null;
consumer = null;
}
// -- Helper classes --
class TileConsumer implements ImageConsumer {
private int width, height;
private ImageProducer producer;
private int yy = 0, hh = 0;
public TileConsumer(ImageProducer producer) {
this.producer = producer;
}
public TileConsumer(ImageProducer producer, int y, int h) {
this(producer);
this.yy = y;
this.hh = h;
}
// -- TileConsumer API methods --
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
// -- ImageConsumer API methods --
public void imageComplete(int status) {
producer.removeConsumer(this);
}
public void setDimensions(int width, int height) {
this.width = width;
this.height = height;
if (hh <= 0) hh = height;
}
public void setPixels(int x, int y, int w, int h, ColorModel model,
byte[] pixels, int off, int scanSize)
{
LOGGER.debug("Storing row {} of {} ({}%)", new Object[] {y, height,
((double) y / height) * 100.0});
if (y >= (yy + hh)) {
imageComplete(0);
return;
}
else if (y < yy) return;
try {
tiles.add(pixels, x, y, w, h);
}
catch (FormatException e) {
LOGGER.debug("", e);
}
catch (IOException e) {
LOGGER.debug("", e);
}
}
public void setPixels(int x, int y, int w, int h, ColorModel model,
int[] pixels, int off, int scanSize)
{
LOGGER.debug("Storing row {} of {} ({}%)", new Object[] {y, (yy + hh),
((double) y / (yy + hh)) * 100.0});
if (y >= (yy + hh)) {
imageComplete(0);
return;
}
else if (y < yy) return;
try {
tiles.add(pixels, x, y, w, h);
}
catch (FormatException e) {
LOGGER.debug("", e);
}
catch (IOException e) {
LOGGER.debug("", e);
}
}
public void setProperties(Hashtable props) { }
public void setColorModel(ColorModel model) { }
public void setHints(int hintFlags) { }
}
class TileCache {
private static final int ROW_COUNT = 256;
private Hashtable<Region, byte[]> compressedTiles =
new Hashtable<Region, byte[]>();
private JPEGCodec codec = new JPEGCodec();
private CodecOptions options = new CodecOptions();
private ByteVector toCompress = new ByteVector();
private int row = 0;
private Region lastRegion = null;
private byte[] lastTile = null;
private int yy = 0, hh = 0;
public TileCache(int yy, int hh) {
options.interleaved = true;
options.littleEndian = false;
this.yy = yy;
this.hh = hh;
}
public void add(byte[] pixels, int x, int y, int w, int h)
throws FormatException, IOException
{
toCompress.add(pixels);
row++;
if ((y % ROW_COUNT) == ROW_COUNT - 1 || y == getHeight() - 1 ||
y == yy + hh - 1)
{
Region r = new Region(x, y - row + 1, w, row);
options.width = w;
options.height = row;
options.channels = 1;
options.bitsPerSample = 8;
options.signed = false;
byte[] compressed = codec.compress(toCompress.toByteArray(), options);
compressedTiles.put(r, compressed);
toCompress.clear();
}
}
public void add(int[] pixels, int x, int y, int w, int h)
throws FormatException, IOException
{
byte[] buf = new byte[pixels.length * 3];
for (int i=0; i<pixels.length; i++) {
buf[i * 3] = (byte) ((pixels[i] & 0xff0000) >> 16);
buf[i * 3 + 1] = (byte) ((pixels[i] & 0xff00) >> 8);
buf[i * 3 + 2] = (byte) (pixels[i] & 0xff);
}
toCompress.add(buf);
row++;
if ((y % ROW_COUNT) == ROW_COUNT - 1 || y == getHeight() - 1 ||
y == yy + hh - 1)
{
Region r = new Region(x, y - row + 1, w, row);
options.width = w;
options.height = row;
options.channels = 3;
options.bitsPerSample = 8;
options.signed = false;
byte[] compressed = codec.compress(toCompress.toByteArray(), options);
compressedTiles.put(r, compressed);
toCompress.clear();
row = 0;
}
}
public byte[] get(int x, int y, int w, int h)
throws FormatException, IOException
{
Region[] keys = compressedTiles.keySet().toArray(new Region[0]);
Region r = new Region(x, y, w, h);
for (Region key : keys) {
if (key.intersects(r)) {
r = key;
}
}
if (!r.equals(lastRegion)) {
lastRegion = r;
byte[] compressed = null;
compressed = compressedTiles.get(r);
if (compressed == null) return null;
lastTile = codec.decompress(compressed, options);
}
int pixel = options.channels * (options.bitsPerSample / 8);
byte[] buf = new byte[w * h * pixel];
for (int i=0; i<h; i++) {
System.arraycopy(lastTile, r.width * pixel * (i + y - r.y) + (x - r.x),
buf, i * w * pixel, pixel * w);
}
return buf;
}
}
}