| /* |
| * Copyright (c) 2014-2021 by Wen Yu |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0. |
| * |
| * This Source Code may also be made available under the following Secondary |
| * Licenses when the conditions for such availability set forth in the Eclipse |
| * Public License, v. 2.0 are satisfied: GNU General Public License, version 2 |
| * or any later version. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later |
| * |
| * Change History - most recent changes go on top of previous changes |
| * |
| * BMPMeta.java |
| * |
| * Who Date Description |
| * ==== ========= ================================================= |
| * WY 14Mar2015 Initial creation |
| */ |
| |
| package pixy.meta.bmp; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import pixy.meta.Metadata; |
| import pixy.meta.MetadataEntry; |
| import pixy.meta.MetadataType; |
| import pixy.meta.image.ImageMetadata; |
| import pixy.image.bmp.BmpCompression; |
| import pixy.io.IOUtils; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * BMP image tweaking tool |
| * |
| * @author Wen Yu, yuwen_66@yahoo.com |
| * @version 1.0 12/29/2014 |
| */ |
| public class BMPMeta { |
| // Obtain a logger instance |
| private static final Logger LOGGER = LoggerFactory.getLogger(BMPMeta.class); |
| |
| // Data transfer object for multiple thread support |
| private static class DataTransferObject { |
| private byte[] fileHeader; // 14 |
| private byte[] infoHeader; // 40 |
| private int[] colorPalette; |
| } |
| |
| private static void readHeader(InputStream is, DataTransferObject DTO) throws IOException { |
| DTO.fileHeader = new byte[14]; |
| DTO.infoHeader = new byte[40]; |
| |
| is.read(DTO.fileHeader); |
| is.read(DTO.infoHeader); |
| } |
| |
| public static Map<MetadataType, Metadata> readMetadata(InputStream is) throws IOException { |
| Map<MetadataType, Metadata> metadataMap = new HashMap<MetadataType, Metadata>(); |
| ImageMetadata imageMeta = new ImageMetadata(); |
| // Create a new data transfer object to hold data |
| DataTransferObject DTO = new DataTransferObject(); |
| readHeader(is, DTO); |
| |
| LOGGER.info("... BMP Image Inforamtion starts..."); |
| LOGGER.info("Image signature: {}", new String(DTO.fileHeader, 0, 2)); |
| LOGGER.info("File size: {} bytes", IOUtils.readInt(DTO.fileHeader, 2)); |
| LOGGER.info("Reserved1 (2 bytes): {}", IOUtils.readShort(DTO.fileHeader, 6)); |
| LOGGER.info("Reserved2 (2 bytes): {}", IOUtils.readShort(DTO.fileHeader, 8)); |
| LOGGER.info("Data offset: {}", IOUtils.readInt(DTO.fileHeader, 10)); |
| |
| MetadataEntry header = new MetadataEntry("BMP File Header", "Bitmap File Header", true); |
| header.addEntry(new MetadataEntry("Image signature", new String(DTO.fileHeader, 0, 2))); |
| header.addEntry(new MetadataEntry("File size", IOUtils.readInt(DTO.fileHeader, 2) + " bytes")); |
| header.addEntry(new MetadataEntry("Reserved1", IOUtils.readShort(DTO.fileHeader, 6)+"")); |
| header.addEntry(new MetadataEntry("Reserved2", IOUtils.readShort(DTO.fileHeader, 8)+"")); |
| header.addEntry(new MetadataEntry("Data-offset", "byte " + IOUtils.readInt(DTO.fileHeader, 10))); |
| |
| imageMeta.addMetadataEntry(header); |
| |
| // TODO add more ImageMetadata elements |
| LOGGER.info("Info header length: {}", IOUtils.readInt(DTO.infoHeader, 0)); |
| LOGGER.info("Image width: {}", IOUtils.readInt(DTO.infoHeader, 4)); |
| LOGGER.info("Image heigth: {}", IOUtils.readInt(DTO.infoHeader, 8)); |
| |
| String alignment = ""; |
| if(IOUtils.readInt(DTO.infoHeader, 8) > 0) |
| alignment = "BOTTOM_UP" ; |
| else |
| alignment = "TOP_DOWN"; |
| |
| MetadataEntry infoHeader = new MetadataEntry("BMP Info Header", "Bitmap Information Header", true); |
| infoHeader.addEntry(new MetadataEntry("Info-header-lengthen", IOUtils.readInt(DTO.infoHeader, 0) + " bytes")); |
| infoHeader.addEntry(new MetadataEntry("Image-alignment", alignment)); |
| infoHeader.addEntry(new MetadataEntry("Number-of-planes", IOUtils.readShort(DTO.infoHeader, 12) + " planes")); |
| infoHeader.addEntry(new MetadataEntry("Bits-per-pixel", IOUtils.readShort(DTO.infoHeader, 14) + " bits per pixel")); |
| infoHeader.addEntry(new MetadataEntry("Compression", BmpCompression.fromInt(IOUtils.readInt(DTO.infoHeader, 16)).toString())); |
| infoHeader.addEntry(new MetadataEntry("Compessed-image-size", IOUtils.readInt(DTO.infoHeader, 20) + " bytes")); |
| infoHeader.addEntry(new MetadataEntry("Horizontal-resolution", IOUtils.readInt(DTO.infoHeader, 24) + " pixels/meter")); |
| infoHeader.addEntry(new MetadataEntry("Vertical-resolution", IOUtils.readInt(DTO.infoHeader, 28) + " pixels/meter")); |
| infoHeader.addEntry(new MetadataEntry("Colors-used", IOUtils.readInt(DTO.infoHeader, 32) + " colors used")); |
| infoHeader.addEntry(new MetadataEntry("Important-colors", IOUtils.readInt(DTO.infoHeader, 36) + " important colors")); |
| |
| imageMeta.addMetadataEntry(infoHeader); |
| |
| LOGGER.info("Image alignment: {}", alignment); |
| LOGGER.info("Number of planes: {}", IOUtils.readShort(DTO.infoHeader, 12)); |
| LOGGER.info("BitCount (bits per pixel): {}", IOUtils.readShort(DTO.infoHeader, 14)); |
| LOGGER.info("Compression: {}", BmpCompression.fromInt(IOUtils.readInt(DTO.infoHeader, 16))); |
| LOGGER.info("Image size (compressed size of image): {} bytes", IOUtils.readInt(DTO.infoHeader, 20)); |
| LOGGER.info("Horizontal resolution (Pixels/meter): {}", IOUtils.readInt(DTO.infoHeader, 24)); |
| LOGGER.info("Vertical resolution (Pixels/meter): {}", IOUtils.readInt(DTO.infoHeader, 28)); |
| LOGGER.info("Colors used (number of actually used colors): {}", IOUtils.readInt(DTO.infoHeader, 32)); |
| LOGGER.info("Important colors (number of important colors): {}", IOUtils.readInt(DTO.infoHeader, 36)); |
| |
| int bitsPerPixel = IOUtils.readShort(DTO.infoHeader, 14); |
| |
| if(bitsPerPixel <= 8) { |
| readPalette(is, DTO); |
| LOGGER.info("Color map follows"); |
| } |
| |
| metadataMap.put(MetadataType.IMAGE, imageMeta); |
| |
| return metadataMap; |
| } |
| |
| private static void readPalette(InputStream is, DataTransferObject DTO) throws IOException { |
| int index = 0, bindex = 0; |
| int colorsUsed = IOUtils.readInt(DTO.infoHeader, 32); |
| int bitsPerPixel = IOUtils.readShort(DTO.infoHeader, 14); |
| int dataOffset = IOUtils.readInt(DTO.fileHeader, 10); |
| int numOfColors = (colorsUsed == 0)?(1<<bitsPerPixel):colorsUsed; |
| byte palette[] = new byte[numOfColors*4]; |
| DTO.colorPalette = new int[numOfColors]; |
| |
| IOUtils.readFully(is, palette); |
| |
| for(int i = 0; i < numOfColors; i++) |
| { |
| DTO.colorPalette[index++] = ((0xff<<24)|(palette[bindex]&0xff)|((palette[bindex+1]&0xff)<<8)|((palette[bindex+2]&0xff)<<16)); |
| bindex += 4; |
| } |
| // There may be some extra bytes between colorPalette and actual image data |
| IOUtils.skipFully(is, dataOffset - numOfColors*4 - 54); |
| } |
| |
| private BMPMeta() {} |
| } |