266 lines
7.4 KiB
Java
266 lines
7.4 KiB
Java
package ar.com.hjg.pngj;
|
|
|
|
import java.util.zip.Checksum;
|
|
|
|
/**
|
|
* Simple immutable wrapper for basic image info.
|
|
* <p>
|
|
* Some parameters are redundant, but the constructor receives an 'orthogonal'
|
|
* subset.
|
|
* <p>
|
|
* ref: http://www.w3.org/TR/PNG/#11IHDR
|
|
*/
|
|
public class ImageInfo {
|
|
|
|
/**
|
|
* Absolute allowed maximum value for rows and cols (2^24 ~16 million).
|
|
* (bytesPerRow must fit in a 32bit integer,
|
|
* though total amount of pixels not necessarily).
|
|
*/
|
|
public static final int MAX_COLS_ROW = 16777216;
|
|
|
|
/**
|
|
* Cols= Image width, in pixels.
|
|
*/
|
|
public final int cols;
|
|
|
|
/**
|
|
* Rows= Image height, in pixels
|
|
*/
|
|
public final int rows;
|
|
|
|
/**
|
|
* Bits per sample (per channel) in the buffer (1-2-4-8-16). This is 8-16
|
|
* for RGB/ARGB images, 1-2-4-8 for grayscale.
|
|
* For indexed images, number of bits per palette index (1-2-4-8)
|
|
*/
|
|
public final int bitDepth;
|
|
|
|
/**
|
|
* Number of channels, as used internally: 3 for RGB, 4 for RGBA, 2 for GA
|
|
* (gray with alpha), 1 for grayscale or
|
|
* indexed.
|
|
*/
|
|
public final int channels;
|
|
|
|
/**
|
|
* Flag: true if has alpha channel (RGBA/GA)
|
|
*/
|
|
public final boolean alpha;
|
|
|
|
/**
|
|
* Flag: true if is grayscale (G/GA)
|
|
*/
|
|
public final boolean greyscale;
|
|
|
|
/**
|
|
* Flag: true if image is indexed, i.e., it has a palette
|
|
*/
|
|
public final boolean indexed;
|
|
|
|
/**
|
|
* Flag: true if image internally uses less than one byte per sample (bit
|
|
* depth 1-2-4)
|
|
*/
|
|
public final boolean packed;
|
|
|
|
/**
|
|
* Bits used for each pixel in the buffer: channel * bitDepth
|
|
*/
|
|
public final int bitspPixel;
|
|
|
|
/**
|
|
* rounded up value: this is only used internally for filter
|
|
*/
|
|
public final int bytesPixel;
|
|
|
|
/**
|
|
* ceil(bitspp*cols/8) - does not include filter
|
|
*/
|
|
public final int bytesPerRow;
|
|
|
|
/**
|
|
* Equals cols * channels
|
|
*/
|
|
public final int samplesPerRow;
|
|
|
|
/**
|
|
* Amount of "packed samples" : when several samples are stored in a single
|
|
* byte (bitdepth 1,2 4) they are counted as
|
|
* one "packed sample". This is less that samplesPerRow only when bitdepth
|
|
* is 1-2-4 (flag packed = true)
|
|
* <p>
|
|
* This equals the number of elements in the scanline array if working with
|
|
* packedMode=true
|
|
* <p>
|
|
* For internal use, client code should rarely access this.
|
|
*/
|
|
public final int samplesPerRowPacked;
|
|
|
|
private long totalPixels = -1; // lazy getter
|
|
|
|
private long totalRawBytes = -1; // lazy getter
|
|
|
|
/**
|
|
* Short constructor: assumes truecolor (RGB/RGBA)
|
|
*/
|
|
public ImageInfo(int cols, int rows, int bitdepth, boolean alpha) {
|
|
this(cols, rows, bitdepth, alpha, false, false);
|
|
}
|
|
|
|
/**
|
|
* Full constructor
|
|
*
|
|
* @param cols
|
|
* Width in pixels
|
|
* @param rows
|
|
* Height in pixels
|
|
* @param bitdepth
|
|
* Bits per sample, in the buffer : 8-16 for RGB true color and
|
|
* greyscale
|
|
* @param alpha
|
|
* Flag: has an alpha channel (RGBA or GA)
|
|
* @param grayscale
|
|
* Flag: is gray scale (any bitdepth, with or without alpha)
|
|
* @param indexed
|
|
* Flag: has palette
|
|
*/
|
|
public ImageInfo(int cols, int rows, int bitdepth, boolean alpha, boolean grayscale, boolean indexed) {
|
|
this.cols = cols;
|
|
this.rows = rows;
|
|
this.alpha = alpha;
|
|
this.indexed = indexed;
|
|
this.greyscale = grayscale;
|
|
if (greyscale && indexed)
|
|
throw new PngjException("palette and greyscale are mutually exclusive");
|
|
this.channels = (grayscale || indexed) ? (alpha ? 2 : 1) : (alpha ? 4 : 3);
|
|
// http://www.w3.org/TR/PNG/#11IHDR
|
|
this.bitDepth = bitdepth;
|
|
this.packed = bitdepth < 8;
|
|
this.bitspPixel = (channels * this.bitDepth);
|
|
this.bytesPixel = (bitspPixel + 7) / 8;
|
|
this.bytesPerRow = (bitspPixel * cols + 7) / 8;
|
|
this.samplesPerRow = channels * this.cols;
|
|
this.samplesPerRowPacked = packed ? bytesPerRow : samplesPerRow;
|
|
// several checks
|
|
switch (this.bitDepth) {
|
|
case 1:
|
|
case 2:
|
|
case 4:
|
|
if (!(this.indexed || this.greyscale))
|
|
throw new PngjException("only indexed or grayscale can have bitdepth=" + this.bitDepth);
|
|
break;
|
|
case 8:
|
|
break;
|
|
case 16:
|
|
if (this.indexed)
|
|
throw new PngjException("indexed can't have bitdepth=" + this.bitDepth);
|
|
break;
|
|
default:
|
|
throw new PngjException("invalid bitdepth=" + this.bitDepth);
|
|
}
|
|
if (cols < 1 || cols > MAX_COLS_ROW)
|
|
throw new PngjException("invalid cols=" + cols + " ???");
|
|
if (rows < 1 || rows > MAX_COLS_ROW)
|
|
throw new PngjException("invalid rows=" + rows + " ???");
|
|
if (samplesPerRow < 1)
|
|
throw new PngjException("invalid image parameters (overflow?)");
|
|
}
|
|
|
|
/**
|
|
* returns a copy with different size
|
|
*
|
|
* @param cols
|
|
* if non-positive, the original is used
|
|
* @param rows
|
|
* if non-positive, the original is used
|
|
* @return a new copy with the specified size and same properties
|
|
*/
|
|
public ImageInfo withSize(int cols, int rows) {
|
|
return new ImageInfo(cols > 0 ? cols : this.cols, rows > 0 ? rows : this.rows, this.bitDepth, this.alpha, this.greyscale, this.indexed);
|
|
}
|
|
|
|
public long getTotalPixels() {
|
|
if (totalPixels < 0)
|
|
totalPixels = cols * (long) rows;
|
|
return totalPixels;
|
|
}
|
|
|
|
/**
|
|
* Total uncompressed bytes in IDAT, including filter byte. This is not
|
|
* valid for interlaced.
|
|
*/
|
|
public long getTotalRawBytes() {
|
|
if (totalRawBytes < 0)
|
|
totalRawBytes = (bytesPerRow + 1) * (long) rows;
|
|
return totalRawBytes;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "ImageInfo [cols=" + cols + ", rows=" + rows + ", bitDepth=" + bitDepth + ", channels=" + channels + ", alpha=" + alpha + ", greyscale=" + greyscale + ", indexed=" + indexed + "]";
|
|
}
|
|
|
|
/**
|
|
* Brief info: COLSxROWS[dBITDEPTH][a][p][g] ( the default dBITDEPTH='d8' is
|
|
* ommited)
|
|
**/
|
|
public String toStringBrief() {
|
|
return String.valueOf(cols) + "x" + rows + (bitDepth != 8 ? ("d" + bitDepth) : "") + (alpha ? "a" : "") + (indexed ? "p" : "") + (greyscale ? "g" : "");
|
|
}
|
|
|
|
public String toStringDetail() {
|
|
return "ImageInfo [cols=" + cols + ", rows=" + rows + ", bitDepth=" + bitDepth + ", channels=" + channels + ", bitspPixel=" + bitspPixel + ", bytesPixel=" + bytesPixel + ", bytesPerRow=" + bytesPerRow + ", samplesPerRow=" + samplesPerRow + ", samplesPerRowP=" + samplesPerRowPacked + ", alpha=" + alpha + ", greyscale=" + greyscale + ", indexed=" + indexed + ", packed=" + packed + "]";
|
|
}
|
|
|
|
void updateCrc(Checksum crc) {
|
|
crc.update((byte) rows);
|
|
crc.update((byte) (rows >> 8));
|
|
crc.update((byte) (rows >> 16));
|
|
crc.update((byte) cols);
|
|
crc.update((byte) (cols >> 8));
|
|
crc.update((byte) (cols >> 16));
|
|
crc.update((byte) (bitDepth));
|
|
crc.update((byte) (indexed ? 1 : 2));
|
|
crc.update((byte) (greyscale ? 3 : 4));
|
|
crc.update((byte) (alpha ? 3 : 4));
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
final int prime = 31;
|
|
int result = 1;
|
|
result = prime * result + (alpha ? 1231 : 1237);
|
|
result = prime * result + bitDepth;
|
|
result = prime * result + cols;
|
|
result = prime * result + (greyscale ? 1231 : 1237);
|
|
result = prime * result + (indexed ? 1231 : 1237);
|
|
result = prime * result + rows;
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj)
|
|
return true;
|
|
if (obj == null)
|
|
return false;
|
|
if (getClass() != obj.getClass())
|
|
return false;
|
|
ImageInfo other = (ImageInfo) obj;
|
|
if (alpha != other.alpha)
|
|
return false;
|
|
if (bitDepth != other.bitDepth)
|
|
return false;
|
|
if (cols != other.cols)
|
|
return false;
|
|
if (greyscale != other.greyscale)
|
|
return false;
|
|
if (indexed != other.indexed)
|
|
return false;
|
|
if (rows != other.rows)
|
|
return false;
|
|
return true;
|
|
}
|
|
}
|