/*
* #%L
* BSD implementations of Bio-Formats readers and writers
* %%
* Copyright (C) 2005 - 2015 Open Microscopy Environment:
* - Board of Regents of the University of Wisconsin-Madison
* - Glencoe Software, Inc.
* - University of Dundee
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. 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.
*
* 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 HOLDERS 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.
* #L%
*/
package loci.formats.in;
import java.io.IOException;
import loci.common.RandomAccessInputStream;
import loci.formats.CoreMetadata;
import loci.formats.FormatException;
import loci.formats.FormatReader;
import loci.formats.FormatTools;
import loci.formats.ImageTools;
import loci.formats.MetadataTools;
import loci.formats.UnsupportedCompressionException;
import loci.formats.meta.MetadataStore;
import ome.xml.model.primitives.PositiveFloat;
import ome.units.quantity.Length;
import ome.units.UNITS;
/**
* BMPReader is the file format reader for Microsoft Bitmap (BMP) files.
* See http://astronomy.swin.edu.au/~pbourke/dataformats/bmp/ for a nice
* description of the BMP file format.
*
* @author Melissa Linkert melissa at glencoesoftware.com
*/
public class BMPReader extends FormatReader {
// -- Constants --
public static final String BMP_MAGIC_STRING = "BM";
/** Compression types. */
private static final int RAW = 0;
private static final int RLE_8 = 1;
private static final int RLE_4 = 2;
private static final int RGB_MASK = 3;
// -- Fields --
/** Number of bits per pixel. */
private int bpp;
/** The palette for indexed color images. */
private byte[][] palette;
/** Compression type */
private int compression;
/** Offset to image data. */
private long global;
private boolean invertY = false;
// -- Constructor --
/** Constructs a new BMP reader. */
public BMPReader() {
super("Windows Bitmap", "bmp");
domains = new String[] {FormatTools.GRAPHICS_DOMAIN};
}
// -- IFormatReader API methods --
/* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */
@Override
public boolean isThisType(RandomAccessInputStream stream) throws IOException {
final int blockLen = 2;
if (!FormatTools.validStream(stream, blockLen, false)) return false;
return stream.readString(blockLen).startsWith(BMP_MAGIC_STRING);
}
/* @see loci.formats.IFormatReader#get8BitLookupTable() */
@Override
public byte[][] get8BitLookupTable() throws FormatException, IOException {
FormatTools.assertId(currentId, true, 1);
return palette;
}
/**
* @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int)
*/
@Override
public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h)
throws FormatException, IOException
{
FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h);
if (compression != RAW && in.length() < FormatTools.getPlaneSize(this)) {
throw new UnsupportedCompressionException(compression + " not supported");
}
int rowsToSkip = invertY ? y : getSizeY() - (h + y);
int rowLength = (getSizeX() * (isIndexed() ? 1 : getSizeC()) * bpp) / 8;
in.seek(global + rowsToSkip * rowLength);
int pad = ((rowLength * bpp) / 8) % 2;
if (pad == 0) pad = ((rowLength * bpp) / 8) % 4;
else pad *= getSizeC();
int planeSize = getSizeX() * getSizeC() * h;
if (bpp >= 8) planeSize *= (bpp / 8);
else planeSize /= (8 / bpp);
planeSize += pad * h;
if (planeSize + in.getFilePointer() + rowsToSkip * pad > in.length()) {
planeSize -= (pad * h);
// sometimes we have RGB images with a single padding byte
if (planeSize + getSizeY() + in.getFilePointer() <= in.length()) {
pad = 1;
planeSize += h;
}
else {
pad = 0;
}
}
in.skipBytes(rowsToSkip * pad);
int effectiveC = palette != null && palette[0].length > 0 ? 1 : getSizeC();
for (int row=h-1; row>=0; row--) {
int rowIndex = invertY ? h - 1 - row : row;
in.skipBits(x * bpp * effectiveC);
for (int i=0; i<w*effectiveC; i++) {
if (bpp <= 8) {
buf[rowIndex * w * effectiveC + i] = (byte) (in.readBits(bpp) & 0xff);
}
else {
for (int b=0; b<bpp/8; b++) {
buf[(bpp / 8) * (rowIndex * w * effectiveC + i) + b] =
(byte) (in.readBits(8) & 0xff);
}
}
}
if (row > 0) {
int nBits = (getSizeX() - w - x) * bpp * effectiveC + pad * 8;
if (in.getFilePointer() + (nBits / 8) < in.length()) {
in.skipBits(nBits);
}
else {
break;
}
}
}
if (getRGBChannelCount() > 1) {
ImageTools.bgrToRgb(buf, isInterleaved(), 1, getRGBChannelCount());
}
return buf;
}
/* @see loci.formats.IFormatReader#close(boolean) */
@Override
public void close(boolean fileOnly) throws IOException {
super.close(fileOnly);
if (!fileOnly) {
bpp = compression = 0;
global = 0;
palette = null;
invertY = false;
}
}
// -- Internel FormatReader API methods --
/* @see loci.formats.FormatReader#initFile(String) */
@Override
protected void initFile(String id) throws FormatException, IOException {
super.initFile(id);
in = new RandomAccessInputStream(id);
CoreMetadata m = core.get(0);
LOGGER.info("Reading bitmap header");
in.order(true);
// read the first header - 14 bytes
addGlobalMeta("Magic identifier", in.readString(2));
addGlobalMeta("File size (in bytes)", in.readInt());
in.skipBytes(4);
global = in.readInt();
// read the second header - 40 bytes
in.skipBytes(4);
// get the dimensions
m.sizeX = in.readInt();
m.sizeY = in.readInt();
if (getSizeX() < 1) {
LOGGER.trace("Invalid width: {}; using the absolute value", getSizeX());
m.sizeX = Math.abs(getSizeX());
}
if (getSizeY() < 1) {
LOGGER.trace("Invalid height: {}; using the absolute value", getSizeY());
m.sizeY = Math.abs(getSizeY());
invertY = true;
}
addGlobalMeta("Color planes", in.readShort());
bpp = in.readShort();
compression = in.readInt();
in.skipBytes(4);
int pixelSizeX = in.readInt();
int pixelSizeY = in.readInt();
int nColors = in.readInt();
if (nColors == 0 && bpp != 32 && bpp != 24) {
nColors = bpp < 8 ? 1 << bpp : 256;
}
in.skipBytes(4);
// read the palette, if it exists
if (nColors != 0 && bpp == 8) {
palette = new byte[3][256];
for (int i=0; i<nColors; i++) {
for (int j=palette.length-1; j>=0; j--) {
palette[j][i] = in.readByte();
}
in.skipBytes(1);
}
}
else if (nColors != 0) in.skipBytes(nColors * 4);
LOGGER.info("Populating metadata");
m.sizeC = bpp != 24 ? 1 : 3;
if (bpp == 32) m.sizeC = 4;
if (bpp > 8) bpp /= getSizeC();
switch (bpp) {
case 16:
m.pixelType = FormatTools.UINT16;
break;
case 32:
m.pixelType = FormatTools.UINT32;
break;
default:
m.pixelType = FormatTools.UINT8;
}
m.rgb = getSizeC() > 1;
m.littleEndian = true;
m.interleaved = true;
m.imageCount = 1;
m.sizeZ = 1;
m.sizeT = 1;
m.dimensionOrder = "XYCTZ";
m.metadataComplete = true;
m.indexed = palette != null;
if (isIndexed()) {
m.sizeC = 1;
m.rgb = false;
}
m.falseColor = false;
if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) {
addGlobalMeta("Indexed color", palette != null);
addGlobalMeta("Image width", getSizeX());
addGlobalMeta("Image height", getSizeY());
addGlobalMeta("Bits per pixel", bpp);
String comp = "invalid";
switch (compression) {
case RAW:
comp = "None";
break;
case RLE_8:
comp = "8 bit run length encoding";
break;
case RLE_4:
comp = "4 bit run length encoding";
break;
case RGB_MASK:
comp = "RGB bitmap with mask";
break;
}
addGlobalMeta("Compression type", comp);
addGlobalMeta("X resolution", pixelSizeX);
addGlobalMeta("Y resolution", pixelSizeY);
}
// Populate metadata store.
MetadataStore store = makeFilterMetadata();
MetadataTools.populatePixels(store, this);
if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) {
// resolution is stored as pixels per meter; we want to convert to
// microns per pixel
double correctedX = pixelSizeX == 0 ? 0.0 : 1000000.0 / pixelSizeX;
double correctedY = pixelSizeY == 0 ? 0.0 : 1000000.0 / pixelSizeY;
Length sizeX = FormatTools.getPhysicalSizeX(correctedX);
Length sizeY = FormatTools.getPhysicalSizeY(correctedY);
if (sizeX != null) {
store.setPixelsPhysicalSizeX(sizeX, 0);
}
if (sizeY != null) {
store.setPixelsPhysicalSizeY(sizeY, 0);
}
}
}
}