com.drew.imaging.png.PngChunkType Maven / Gradle / Ivy
/*
* Copyright 2002-2017 Drew Noakes
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* More information about this project is available at:
*
* https://drewnoakes.com/code/exif/
* https://github.com/drewnoakes/metadata-extractor
*/
package com.drew.imaging.png;
import com.drew.lang.annotations.NotNull;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* @author Drew Noakes https://drewnoakes.com
*/
public class PngChunkType
{
private static final Set _identifiersAllowingMultiples
= new HashSet(Arrays.asList("IDAT", "sPLT", "iTXt", "tEXt", "zTXt"));
//
// Standard critical chunks
//
/**
* Denotes a critical {@link PngChunk} that contains basic information about the PNG image.
* This must be the first chunk in the data sequence, and may only occur once.
*
* The format is:
*
* - pixel width 4 bytes, unsigned and greater than zero
* - pixel height 4 bytes, unsigned and greater than zero
* - bit depth 1 byte, number of bits per sample or per palette index (not per pixel)
* - color type 1 byte, maps to {@link PngColorType} enum
* - compression method 1 byte, currently only a value of zero (deflate/inflate) is in the standard
* - filter method 1 byte, currently only a value of zero (adaptive filtering with five basic filter types) is in the standard
* - interlace method 1 byte, indicates the transmission order of image data, currently only 0 (no interlace) and 1 (Adam7 interlace) are in the standard
*
*/
public static final PngChunkType IHDR;
/**
* Denotes a critical {@link PngChunk} that contains palette entries.
* This chunk should only appear for a {@link PngColorType} of IndexedColor
,
* and may only occur once in the PNG data sequence.
*
* The chunk contains between one and 256 entries, each of three bytes:
*
* - red 1 byte
* - green 1 byte
* - blue 1 byte
*
* The number of entries is determined by the chunk length. A chunk length indivisible by three is an error.
*/
public static final PngChunkType PLTE;
public static final PngChunkType IDAT;
public static final PngChunkType IEND;
//
// Standard ancillary chunks
//
public static final PngChunkType cHRM;
public static final PngChunkType gAMA;
public static final PngChunkType iCCP;
public static final PngChunkType sBIT;
public static final PngChunkType sRGB;
public static final PngChunkType bKGD;
public static final PngChunkType hIST;
public static final PngChunkType tRNS;
public static final PngChunkType pHYs;
public static final PngChunkType sPLT;
public static final PngChunkType tIME;
public static final PngChunkType iTXt;
/**
* Denotes an ancillary {@link PngChunk} that contains textual data, having first a keyword and then a value.
* If multiple text data keywords are needed, then multiple chunks are included in the PNG data stream.
*
* The format is:
*
* - keyword 1-79 bytes
* - null separator 1 byte (\0)
* - text string 0 or more bytes
*
* Text is interpreted according to the Latin-1 character set [ISO-8859-1].
* Newlines should be represented by a single linefeed character (0x9).
*/
public static final PngChunkType tEXt;
public static final PngChunkType zTXt;
static {
try {
IHDR = new PngChunkType("IHDR");
PLTE = new PngChunkType("PLTE");
IDAT = new PngChunkType("IDAT", true);
IEND = new PngChunkType("IEND");
cHRM = new PngChunkType("cHRM");
gAMA = new PngChunkType("gAMA");
iCCP = new PngChunkType("iCCP");
sBIT = new PngChunkType("sBIT");
sRGB = new PngChunkType("sRGB");
bKGD = new PngChunkType("bKGD");
hIST = new PngChunkType("hIST");
tRNS = new PngChunkType("tRNS");
pHYs = new PngChunkType("pHYs");
sPLT = new PngChunkType("sPLT", true);
tIME = new PngChunkType("tIME");
iTXt = new PngChunkType("iTXt", true);
tEXt = new PngChunkType("tEXt", true);
zTXt = new PngChunkType("zTXt", true);
} catch (PngProcessingException e) {
throw new IllegalArgumentException(e);
}
}
private final byte[] _bytes;
private final boolean _multipleAllowed;
public PngChunkType(@NotNull String identifier) throws PngProcessingException
{
this(identifier, false);
}
public PngChunkType(@NotNull String identifier, boolean multipleAllowed) throws PngProcessingException
{
_multipleAllowed = multipleAllowed;
try {
byte[] bytes = identifier.getBytes("ASCII");
validateBytes(bytes);
_bytes = bytes;
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("Unable to convert string code to bytes.");
}
}
public PngChunkType(@NotNull byte[] bytes) throws PngProcessingException
{
validateBytes(bytes);
_bytes = bytes;
_multipleAllowed = _identifiersAllowingMultiples.contains(getIdentifier());
}
private static void validateBytes(byte[] bytes) throws PngProcessingException
{
if (bytes.length != 4) {
throw new PngProcessingException("PNG chunk type identifier must be four bytes in length");
}
for (byte b : bytes) {
if (!isValidByte(b)) {
throw new PngProcessingException("PNG chunk type identifier may only contain alphabet characters");
}
}
}
public boolean isCritical()
{
return isUpperCase(_bytes[0]);
}
public boolean isAncillary()
{
return !isCritical();
}
public boolean isPrivate()
{
return isUpperCase(_bytes[1]);
}
public boolean isSafeToCopy()
{
return isLowerCase(_bytes[3]);
}
public boolean areMultipleAllowed()
{
return _multipleAllowed;
}
private static boolean isLowerCase(byte b)
{
return (b & (1 << 5)) != 0;
}
private static boolean isUpperCase(byte b)
{
return (b & (1 << 5)) == 0;
}
private static boolean isValidByte(byte b)
{
return (b >= 65 && b <= 90) || (b >= 97 && b <= 122);
}
public String getIdentifier()
{
try {
return new String(_bytes, "ASCII");
} catch (UnsupportedEncodingException e) {
// The constructor should ensure that we're always able to encode the bytes in ASCII.
// noinspection ConstantConditions
assert(false);
return "Invalid object instance";
}
}
@Override
public String toString()
{
return getIdentifier();
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
PngChunkType that = (PngChunkType)o;
return Arrays.equals(_bytes, that._bytes);
}
@Override
public int hashCode()
{
return Arrays.hashCode(_bytes);
}
}