package net.pms.image;
import java.awt.color.ColorSpace;
import java.awt.image.ColorModel;
import net.pms.util.ParseException;
import com.drew.imaging.png.PngChunkType;
import com.drew.imaging.png.PngColorType;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.png.PngDirectory;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
@SuppressWarnings("serial")
@SuppressFBWarnings("SE_NO_SERIALVERSIONID")
public class PNGInfo extends ImageInfo {
protected final PngColorType colorType;
protected final InterlaceMethod interlaceMethod;
protected final boolean hasTransparencyChunk;
protected final boolean isModifiedBitDepth;
/**
* Use
* {@link ImageInfo#create(int, int, ImageFormat, long, ColorModel, Metadata, boolean, boolean)}
* to instantiate.
*/
protected PNGInfo(
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);
colorType = ((PNGParseInfo) parsedInfo).colorType;
interlaceMethod = ((PNGParseInfo) parsedInfo).interlaceMethod;
hasTransparencyChunk = ((PNGParseInfo) parsedInfo).hasTransparencyChunk;
isModifiedBitDepth = ((PNGParseInfo) parsedInfo).isModifiedBitDepth;
}
/**
* Use
* {@link ImageInfo#create(int, int, ImageFormat, long, int, int, ColorSpace, ColorSpaceType, Metadata, boolean, boolean)}
* to instantiate.
*/
protected PNGInfo(
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
);
colorType = ((PNGParseInfo) parsedInfo).colorType;
interlaceMethod = ((PNGParseInfo) parsedInfo).interlaceMethod;
hasTransparencyChunk = ((PNGParseInfo) parsedInfo).hasTransparencyChunk;
isModifiedBitDepth = ((PNGParseInfo) parsedInfo).isModifiedBitDepth;
}
/**
* Use
* {@link ImageInfo#create(int, int, Metadata, ImageFormat, long, boolean, boolean)}
* to instantiate.
*/
protected PNGInfo(
int width,
int height,
Metadata metadata,
ImageFormat format,
long size,
boolean applyExifOrientation,
boolean throwOnParseFailure
) throws ParseException {
super(width, height, metadata, format, size, applyExifOrientation, throwOnParseFailure);
colorType = ((PNGParseInfo) parsedInfo).colorType;
interlaceMethod = ((PNGParseInfo) parsedInfo).interlaceMethod;
hasTransparencyChunk = ((PNGParseInfo) parsedInfo).hasTransparencyChunk;
isModifiedBitDepth = ((PNGParseInfo) parsedInfo).isModifiedBitDepth;
}
/**
* Copy constructor
*/
protected PNGInfo(
int width,
int height,
ImageFormat format,
long size,
int bitDepth,
int numComponents,
ColorSpace colorSpace,
ColorSpaceType colorSpaceType,
boolean imageIOSupport,
PngColorType colorType,
InterlaceMethod interlaceMethod,
boolean hasTransparencyChunk,
boolean isModifiedBitDepth
) {
super(width, height, format, size, bitDepth, numComponents, colorSpace, colorSpaceType, imageIOSupport);
this.colorType = colorType;
this.interlaceMethod = interlaceMethod;
this.hasTransparencyChunk = hasTransparencyChunk;
this.isModifiedBitDepth = isModifiedBitDepth;
}
/**
* @return The {@link PngColorType} or {@code null} if unknown.
*/
public PngColorType getColorType() {
return colorType;
}
/**
* @return Whether or not the PNG has a tRNS chunk with transparency information.
*/
public boolean isHasTransparencyChunk() {
return hasTransparencyChunk;
}
/**
* @return Whether or not the PNG has a sBIT chunk altering the bit depth.
*/
public boolean isModifiedBitDepth() {
return isModifiedBitDepth;
}
/**
* @return The {@link InterlaceMethod} or {@code null} if unknown.
*/
public InterlaceMethod getInterlaceMethod() {
return interlaceMethod;
}
@Override
protected ParseInfo createParseInfo() {
return new PNGParseInfo();
}
@Override
protected void parseMetadata(Metadata metadata) throws ParseException {
if (metadata == null) {
return;
}
for (Directory directory : metadata.getDirectories()) {
if (directory instanceof PngDirectory && PngChunkType.IHDR.equals(((PngDirectory) directory).getPngChunkType())) {
parsedInfo.format = ImageFormat.PNG;
if (
((PngDirectory) directory).containsTag(PngDirectory.TAG_IMAGE_WIDTH) &&
((PngDirectory) directory).containsTag(PngDirectory.TAG_IMAGE_HEIGHT)
) {
parsedInfo.width = ((PngDirectory) directory).getInteger(PngDirectory.TAG_IMAGE_WIDTH);
parsedInfo.height = ((PngDirectory) directory).getInteger(PngDirectory.TAG_IMAGE_HEIGHT);
}
if (((PngDirectory) directory).containsTag(PngDirectory.TAG_BITS_PER_SAMPLE)) {
parsedInfo.bitDepth = ((PngDirectory) directory).getInteger(PngDirectory.TAG_BITS_PER_SAMPLE);
}
if (((PngDirectory) directory).containsTag(PngDirectory.TAG_INTERLACE_METHOD)) {
Integer i = ((PngDirectory) directory).getInteger(PngDirectory.TAG_INTERLACE_METHOD);
if (i != null) {
((PNGParseInfo) parsedInfo).interlaceMethod = InterlaceMethod.typeOf(i);
}
}
if (((PngDirectory) directory).containsTag(PngDirectory.TAG_COLOR_TYPE)) {
Integer i = ((PngDirectory) directory).getInteger(PngDirectory.TAG_COLOR_TYPE);
if (i != null) {
((PNGParseInfo) parsedInfo).colorType = PngColorType.fromNumericValue(i);
switch (((PNGParseInfo) parsedInfo).colorType) {
case Greyscale: // Grayscale without alpha
parsedInfo.numComponents = 1;
parsedInfo.colorSpaceType = ColorSpaceType.TYPE_GRAY;
break;
case TrueColor: // RGB without alpha
parsedInfo.numComponents = 3;
parsedInfo.colorSpaceType = ColorSpaceType.TYPE_RGB;
break;
case IndexedColor: // Palette index
parsedInfo.numComponents = 3;
parsedInfo.colorSpaceType = ColorSpaceType.TYPE_RGB;
break;
case GreyscaleWithAlpha: // Grayscale with alpha
parsedInfo.numComponents = 2;
parsedInfo.colorSpaceType = ColorSpaceType.TYPE_GRAY;
break;
case TrueColorWithAlpha: // RGB with alpha
parsedInfo.numComponents = 4;
parsedInfo.colorSpaceType = ColorSpaceType.TYPE_RGB;
break;
default:
}
}
}
}
if (directory instanceof PngDirectory && PngChunkType.tRNS.equals(((PngDirectory) directory).getPngChunkType())) {
((PNGParseInfo) parsedInfo).hasTransparencyChunk = true;
if (((PNGParseInfo) parsedInfo).colorType == null || ((PNGParseInfo) parsedInfo).numComponents == null) {
throw new ParseException("PNG parsing failed with ancillary chunk tRNS appearing before critical chunk IHDR");
}
if (
((PNGParseInfo) parsedInfo).colorType == PngColorType.GreyscaleWithAlpha ||
((PNGParseInfo) parsedInfo).colorType == PngColorType.TrueColorWithAlpha
) {
throw new ParseException(String.format(
"PNG parsing failed with illegal combination of %s color type and tRNS transparancy chunk",
((PNGParseInfo) parsedInfo).colorType
));
}
parsedInfo.numComponents++;
}
if (directory instanceof PngDirectory && PngChunkType.sBIT.equals(((PngDirectory) directory).getPngChunkType())) {
((PNGParseInfo) parsedInfo).isModifiedBitDepth = true;
}
}
}
@Override
public PNGInfo copy() {
return new PNGInfo(
width,
height,
format,
size,
bitDepth,
numComponents,
colorSpace,
colorSpaceType,
imageIOSupport,
colorType,
interlaceMethod,
hasTransparencyChunk,
isModifiedBitDepth
);
}
public enum InterlaceMethod {
NONE, ADAM7, UNKNOWN;
public static InterlaceMethod typeOf(int value) {
switch (value) {
case 0:
return NONE;
case 1:
return ADAM7;
default:
return UNKNOWN;
}
}
}
protected static class PNGParseInfo extends ParseInfo {
PngColorType colorType;
InterlaceMethod interlaceMethod;
boolean hasTransparencyChunk = false;
boolean isModifiedBitDepth = false;
}
@Override
protected void buildToString(StringBuilder sb) {
if (colorType != null) {
sb.append(", Color Type = ").append(colorType);
}
if (interlaceMethod != null) {
sb.append(", Interlace Method = ").append(interlaceMethod);
}
sb.append(", Has Transparency Chunk = ").append(hasTransparencyChunk ? "True" : "False")
.append("Has Modified Bit Depth = ").append(isModifiedBitDepth ? "True" : "False");
}
}