// // BMPReader.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.in; import java.io.IOException; import loci.common.RandomAccessInputStream; 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.codec.BitBuffer; import loci.formats.meta.MetadataStore; import ome.xml.model.primitives.PositiveFloat; /** * 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. * * <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/in/BMPReader.java">Trac</a>, * <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/in/BMPReader.java;hb=HEAD">Gitweb</a></dd></dl> * * @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) */ 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() */ public byte[][] get8BitLookupTable() throws FormatException, IOException { FormatTools.assertId(currentId, true, 1); return palette; } /** * @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int) */ 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) { throw new UnsupportedCompressionException(compression + " not supported"); } int rowsToSkip = invertY ? y : getSizeY() - (h + y); int rowLength = getSizeX() * (isIndexed() ? 1 : getSizeC()); 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() > in.length()) { planeSize -= (pad * h); pad = 0; } in.skipBytes(rowsToSkip * pad); byte[] rawPlane = new byte[planeSize]; in.read(rawPlane); BitBuffer bb = new BitBuffer(rawPlane); 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; bb.skipBits(x * bpp * effectiveC); for (int i=0; i<w*effectiveC; i++) { if (bpp <= 8) { buf[rowIndex * w * effectiveC + i] = (byte) (bb.getBits(bpp) & 0xff); } else { for (int b=0; b<bpp/8; b++) { buf[(bpp / 8) * (rowIndex * w * effectiveC + i) + b] = (byte) (bb.getBits(8) & 0xff); } } } if (row > 0) { bb.skipBits((getSizeX() - w - x) * bpp * effectiveC + pad*8); } } if (getRGBChannelCount() > 1) { ImageTools.bgrToRgb(buf, isInterleaved(), 1, getRGBChannelCount()); } return buf; } /* @see loci.formats.IFormatReader#close(boolean) */ 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) */ protected void initFile(String id) throws FormatException, IOException { super.initFile(id); in = new RandomAccessInputStream(id); 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 core[0].sizeX = in.readInt(); core[0].sizeY = in.readInt(); if (getSizeX() < 1) { LOGGER.trace("Invalid width: {}; using the absolute value", getSizeX()); core[0].sizeX = Math.abs(getSizeX()); } if (getSizeY() < 1) { LOGGER.trace("Invalid height: {}; using the absolute value", getSizeY()); core[0].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"); core[0].sizeC = bpp != 24 ? 1 : 3; if (bpp == 32) core[0].sizeC = 4; if (bpp > 8) bpp /= getSizeC(); switch (bpp) { case 16: core[0].pixelType = FormatTools.UINT16; break; case 32: core[0].pixelType = FormatTools.UINT32; break; default: core[0].pixelType = FormatTools.UINT8; } core[0].rgb = getSizeC() > 1; core[0].littleEndian = true; core[0].interleaved = true; core[0].imageCount = 1; core[0].sizeZ = 1; core[0].sizeT = 1; core[0].dimensionOrder = "XYCTZ"; core[0].metadataComplete = true; core[0].indexed = palette != null; if (isIndexed()) { core[0].sizeC = 1; core[0].rgb = false; } core[0].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); MetadataTools.setDefaultCreationDate(store, id, 0); 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; if (correctedX > 0) { store.setPixelsPhysicalSizeX(new PositiveFloat(correctedX), 0); } if (correctedY > 0) { store.setPixelsPhysicalSizeY(new PositiveFloat(correctedY), 0); } } } }