package net.pms.image;
import java.awt.color.ColorSpace;
import java.awt.image.ColorModel;
import net.pms.util.ParseException;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.bmp.BmpHeaderDirectory;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
@SuppressWarnings("serial")
@SuppressFBWarnings("SE_NO_SERIALVERSIONID")
public class BMPInfo extends ImageInfo {
protected final CompressionType compressionType;
/**
* Use
* {@link ImageInfo#create(int, int, ImageFormat, long, ColorModel, Metadata, boolean, boolean)}
* to instantiate.
*/
protected BMPInfo(
int width,
int height,
ImageFormat format,
long size,
ColorModel colorModel,
Metadata metadata,
boolean applyExifOrientation,
boolean imageIOSupport
) throws ParseException {
super(width, height, format, size, colorModel, metadata, applyExifOrientation, imageIOSupport);
compressionType = ((BMPParseInfo) parsedInfo).compressionType;
}
/**
* Use
* {@link ImageInfo#create(int, int, ImageFormat, long, int, int, ColorSpace, ColorSpaceType, Metadata, boolean, boolean)}
* to instantiate.
*/
protected BMPInfo(
int width,
int height,
ImageFormat format,
long size,
int bitDepth,
int numComponents,
ColorSpace colorSpace,
ColorSpaceType colorSpaceType,
Metadata metadata,
boolean applyExifOrientation,
boolean imageIOSupport
) throws ParseException {
super(
width,
height,
format,
size,
bitDepth,
numComponents,
colorSpace,
colorSpaceType,
metadata,
applyExifOrientation,
imageIOSupport
);
compressionType = ((BMPParseInfo) parsedInfo).compressionType;
}
/**
* Use
* {@link ImageInfo#create(int, int, Metadata, ImageFormat, long, boolean, boolean)}
* to instantiate.
*/
protected BMPInfo(
int width,
int height,
Metadata metadata,
ImageFormat format,
long size,
boolean applyExifOrientation,
boolean throwOnParseFailure
) throws ParseException {
super(width, height, metadata, format, size, applyExifOrientation, throwOnParseFailure);
compressionType = ((BMPParseInfo) parsedInfo).compressionType;
}
/**
* Copy constructor
*/
protected BMPInfo(
int width,
int height,
ImageFormat format,
long size,
int bitDepth,
int numComponents,
ColorSpace colorSpace,
ColorSpaceType colorSpaceType,
boolean imageIOSupport,
CompressionType compressionType
) {
super(width, height, format, size, bitDepth, numComponents, colorSpace, colorSpaceType, imageIOSupport);
this.compressionType = compressionType;
}
@Override
protected ParseInfo createParseInfo() {
return new BMPParseInfo();
}
/**
* @return The {@link CompressionType}.
*/
public CompressionType getCompressionType() {
return compressionType;
}
@SuppressFBWarnings("SF_SWITCH_NO_DEFAULT")
@Override
protected void parseMetadata(Metadata metadata) {
if (metadata == null) {
return;
}
for (Directory directory : metadata.getDirectories()) {
if (directory instanceof BmpHeaderDirectory) {
parsedInfo.format = ImageFormat.BMP;
if (
((BmpHeaderDirectory) directory).containsTag(BmpHeaderDirectory.TAG_IMAGE_WIDTH) &&
((BmpHeaderDirectory) directory).containsTag(BmpHeaderDirectory.TAG_IMAGE_HEIGHT)
) {
parsedInfo.width = ((BmpHeaderDirectory) directory).getInteger(BmpHeaderDirectory.TAG_IMAGE_WIDTH);
parsedInfo.height = ((BmpHeaderDirectory) directory).getInteger(BmpHeaderDirectory.TAG_IMAGE_HEIGHT);
}
}
if (
((BmpHeaderDirectory) directory).containsTag(BmpHeaderDirectory.TAG_BITS_PER_PIXEL) &&
((BmpHeaderDirectory) directory).containsTag(BmpHeaderDirectory.TAG_COMPRESSION) &&
((BmpHeaderDirectory) directory).containsTag(BmpHeaderDirectory.TAG_HEADER_SIZE)
) {
Integer compression = ((BmpHeaderDirectory) directory).getInteger(BmpHeaderDirectory.TAG_COMPRESSION);
Integer headerSize = ((BmpHeaderDirectory) directory).getInteger(BmpHeaderDirectory.TAG_HEADER_SIZE);
Integer bitsPerPixel = ((BmpHeaderDirectory) directory).getInteger(BmpHeaderDirectory.TAG_BITS_PER_PIXEL);
if (compression != null && headerSize != null && bitsPerPixel != null) {
CompressionType compressionType =
CompressionType.typeOf(compression.intValue(), headerSize.intValue());
((BMPParseInfo) parsedInfo).compressionType = compressionType;
if (parsedInfo.bitDepth != null) {
switch (compressionType) {
case BIT_FIELDS:
case HUFFMAN_1D:
case NONE:
case PNG:
case RLE24:
case RLE4:
case RLE8:
// XXX Further parsing of the different
// BITMAPINFOHEADER versions is needed for
// accurate detection - the below is
// "qualified guessing"
switch (bitsPerPixel.intValue()) {
case 1:
parsedInfo.numComponents = 1;
parsedInfo.colorSpaceType = ColorSpaceType.TYPE_GRAY;
parsedInfo.bitDepth = 1;
break;
case 2:
case 4:
case 8:
parsedInfo.numComponents = 3;
parsedInfo.colorSpaceType = ColorSpaceType.TYPE_RGB;
parsedInfo.bitDepth = 8;
break;
case 16:
// assuming 5:6:5 - could also be 5:5:5:1
parsedInfo.numComponents = 3;
parsedInfo.colorSpaceType = ColorSpaceType.TYPE_RGB;
parsedInfo.bitDepth = 5;
break;
case 24:
parsedInfo.numComponents = 3;
parsedInfo.colorSpaceType = ColorSpaceType.TYPE_RGB;
parsedInfo.bitDepth = 8;
break;
case 32:
parsedInfo.numComponents = 4;
parsedInfo.colorSpaceType = ColorSpaceType.TYPE_RGB;
parsedInfo.bitDepth = 8;
break;
default:
}
break;
case JPEG:
parsedInfo.numComponents = 3;
parsedInfo.colorSpaceType = ColorSpaceType.TYPE_RGB;
parsedInfo.bitDepth = bitsPerPixel.intValue() / 3;
break;
case RGBA_BIT_FIELDS:
parsedInfo.numComponents = 4;
parsedInfo.colorSpaceType = ColorSpaceType.TYPE_RGB;
parsedInfo.bitDepth = bitsPerPixel.intValue() / 4;
break;
case CMYK_NONE:
case CMYK_RLE4:
case CMYK_RLE8:
parsedInfo.numComponents = 4;
parsedInfo.colorSpaceType = ColorSpaceType.TYPE_CMYK;
parsedInfo.bitDepth = bitsPerPixel.intValue() / 4;
break;
case UNKNOWN:
default:
}
}
}
}
}
}
@Override
public BMPInfo copy() {
return new BMPInfo(
width,
height,
format,
size,
bitDepth,
numComponents,
colorSpace,
colorSpaceType,
imageIOSupport,
compressionType
);
}
public enum CompressionType {
NONE,
RLE8,
RLE4,
BIT_FIELDS,
HUFFMAN_1D,
JPEG,
RLE24,
PNG,
RGBA_BIT_FIELDS,
CMYK_NONE,
CMYK_RLE8,
CMYK_RLE4,
UNKNOWN;
@Override
public String toString() {
switch (this) {
case BIT_FIELDS:
return "RGB bit field masks";
case CMYK_NONE:
return "CMYK no compression";
case CMYK_RLE4:
return "CMYK RLE 4";
case CMYK_RLE8:
return "CMYK RLE 8";
case HUFFMAN_1D:
return "Huffman 1D";
case JPEG:
return "JPEG";
case NONE:
return "No compression";
case PNG:
return "PNG";
case RGBA_BIT_FIELDS:
return "RGBA bit field masks";
case RLE24:
return "RLE 24-bit/pixel";
case RLE4:
return "RLE 4-bit/pixel";
case RLE8:
return "RLE 8-bit/pixel";
case UNKNOWN:
return "Unknown compression";
default:
return super.toString();
}
}
public static CompressionType typeOf(int value, int headerSize) {
switch (value) {
case 0:
return NONE;
case 1:
return RLE8;
case 2:
return RLE4;
case 3:
return headerSize == 64 ? BIT_FIELDS : HUFFMAN_1D;
case 4:
return headerSize == 64 ? JPEG : RLE24;
case 5:
return PNG;
case 6:
return RGBA_BIT_FIELDS;
case 11:
return CMYK_NONE;
case 12:
return CMYK_RLE8;
case 13:
return CMYK_RLE4;
default:
return UNKNOWN;
}
}
}
protected static class BMPParseInfo extends ParseInfo {
CompressionType compressionType;
}
@Override
protected void buildToString(StringBuilder sb) {
if (compressionType != null) {
sb.append(", Compression Type = ").append(compressionType);
}
}
}