package jogamp.opengl.util.pngj; import jogamp.opengl.util.pngj.ImageLineHelper.ImageLineStats; /** * Lightweight wrapper for an image scanline, used for read and write. *
* This object can be (usually it is) reused while iterating over the image * lines. *
* See scanline
field, to understand the format.
*/
public class ImageLine {
public final ImageInfo imgInfo;
/**
* tracks the current row number (from 0 to rows-1)
*/
private int rown = 0;
/**
* The 'scanline' is an array of integers, corresponds to an image line
* (row).
*
* Except for 'packed' formats (gray/indexed with 1-2-4 bitdepth) each
*
* For bitdepth=1/2/4 , and if samplesUnpacked=false, each value is a PACKED
* byte!
*
* To convert a indexed line to RGB balues, see
* int
is a "sample" (one for channel), (0-255 or 0-65535) in
* the corresponding PNG sequence: R G B R G B...
or
* R G B A R G B A...
* or
g g g ...
or i i i
(palette index)
* ImageLineHelper.palIdx2RGB()
(you can't do the reverse)
*/
public final int[] scanline;
/**
* Same as {@link #scanline}, but with one byte per sample. Only one of
* scanline and scanlineb is valid - this depends on {@link #sampleType}
*/
public final byte[] scanlineb;
protected FilterType filterUsed; // informational ; only filled by the reader
final int channels; // copied from imgInfo, more handy
final int bitDepth; // copied from imgInfo, more handy
final int elementsPerRow; // = imgInfo.samplePerRowPacked, if packed:imgInfo.samplePerRow elswhere
public enum SampleType {
INT, // 4 bytes per sample
// SHORT, // 2 bytes per sample
BYTE // 1 byte per sample
}
/**
* tells if we are using BYTE or INT to store the samples.
*/
public final SampleType sampleType;
/**
* true: each element of the scanline array represents a sample always, even
* for internally packed PNG formats
*
* false: if the original image was of packed type (bit depth less than 8)
* we keep samples packed in a single array element
*/
public final boolean samplesUnpacked;
/**
* default mode: INT packed
*/
public ImageLine(final ImageInfo imgInfo) {
this(imgInfo, SampleType.INT, false);
}
/**
*
* @param imgInfo
* Inmutable ImageInfo, basic parameter of the image we are
* reading or writing
* @param stype
* INT or BYTE : this determines which scanline is the really
* used one
* @param unpackedMode
* If true, we use unpacked format, even for packed original
* images
*
*/
public ImageLine(final ImageInfo imgInfo, final SampleType stype, final boolean unpackedMode) {
this(imgInfo, stype, unpackedMode, null, null);
}
/**
* If a preallocated array is passed, the copy is shallow
*/
ImageLine(final ImageInfo imgInfo, final SampleType stype, final boolean unpackedMode, final int[] sci, final byte[] scb) {
this.imgInfo = imgInfo;
channels = imgInfo.channels;
bitDepth = imgInfo.bitDepth;
filterUsed = FilterType.FILTER_UNKNOWN;
this.sampleType = stype;
this.samplesUnpacked = unpackedMode || !imgInfo.packed;
elementsPerRow = this.samplesUnpacked ? imgInfo.samplesPerRow : imgInfo.samplesPerRowPacked;
if (stype == SampleType.INT) {
scanline = sci != null ? sci : new int[elementsPerRow];
scanlineb = null;
} else if (stype == SampleType.BYTE) {
scanlineb = scb != null ? scb : new byte[elementsPerRow];
scanline = null;
} else
throw new PngjExceptionInternal("bad ImageLine initialization");
this.rown = -1;
}
/** This row number inside the image (0 is top) */
public int getRown() {
return rown;
}
/** Sets row number (0 : Rows-1) */
public void setRown(final int n) {
this.rown = n;
}
/*
* Unpacks scanline (for bitdepth 1-2-4)
*
* Arrays must be prealocated. src : samplesPerRowPacked dst : samplesPerRow
*
* This usually works in place (with src==dst and length=samplesPerRow)!
*
* If not, you should only call this only when necesary (bitdepth <8)
*
* If scale==true
, it scales the value (just a bit shift) towards 0-255.
*/
static void unpackInplaceInt(final ImageInfo iminfo, final int[] src, final int[] dst, final boolean scale) {
final int bitDepth = iminfo.bitDepth;
if (bitDepth >= 8)
return; // nothing to do
final int mask0 = ImageLineHelper.getMaskForPackedFormatsLs(bitDepth);
final int scalefactor = 8 - bitDepth;
final int offset0 = 8 * iminfo.samplesPerRowPacked - bitDepth * iminfo.samplesPerRow;
int mask, offset, v;
if (offset0 != 8) {
mask = mask0 << offset0;
offset = offset0; // how many bits to shift the mask to the right to recover mask0
} else {
mask = mask0;
offset = 0;
}
for (int j = iminfo.samplesPerRow - 1, i = iminfo.samplesPerRowPacked - 1; j >= 0; j--) {
v = (src[i] & mask) >> offset;
if (scale)
v <<= scalefactor;
dst[j] = v;
mask <<= bitDepth;
offset += bitDepth;
if (offset == 8) {
mask = mask0;
offset = 0;
i--;
}
}
}
/*
* Unpacks scanline (for bitdepth 1-2-4)
*
* Arrays must be prealocated. src : samplesPerRow dst : samplesPerRowPacked
*
* This usually works in place (with src==dst and length=samplesPerRow)! If not, you should only call this only when
* necesary (bitdepth <8)
*
* The trailing elements are trash
*
*
* If
scale==true
, it scales the value (just a bit shift) towards 0-255.
*/
static void packInplaceInt(final ImageInfo iminfo, final int[] src, final int[] dst, final boolean scaled) {
final int bitDepth = iminfo.bitDepth;
if (bitDepth >= 8)
return; // nothing to do
final int mask0 = ImageLineHelper.getMaskForPackedFormatsLs(bitDepth);
final int scalefactor = 8 - bitDepth;
final int offset0 = 8 - bitDepth;
int v, v0;
int offset = 8 - bitDepth;
v0 = src[0]; // first value is special for in place
dst[0] = 0;
if (scaled)
v0 >>= scalefactor;
v0 = ((v0 & mask0) << offset);
for (int i = 0, j = 0; j < iminfo.samplesPerRow; j++) {
v = src[j];
if (scaled)
v >>= scalefactor;
dst[i] |= ((v & mask0) << offset);
offset -= bitDepth;
if (offset < 0) {
offset = offset0;
i++;
dst[i] = 0;
}
}
dst[0] |= v0;
}
static void unpackInplaceByte(final ImageInfo iminfo, final byte[] src, final byte[] dst, final boolean scale) {
final int bitDepth = iminfo.bitDepth;
if (bitDepth >= 8)
return; // nothing to do
final int mask0 = ImageLineHelper.getMaskForPackedFormatsLs(bitDepth);
final int scalefactor = 8 - bitDepth;
final int offset0 = 8 * iminfo.samplesPerRowPacked - bitDepth * iminfo.samplesPerRow;
int mask, offset, v;
if (offset0 != 8) {
mask = mask0 << offset0;
offset = offset0; // how many bits to shift the mask to the right to recover mask0
} else {
mask = mask0;
offset = 0;
}
for (int j = iminfo.samplesPerRow - 1, i = iminfo.samplesPerRowPacked - 1; j >= 0; j--) {
v = (src[i] & mask) >> offset;
if (scale)
v <<= scalefactor;
dst[j] = (byte) v;
mask <<= bitDepth;
offset += bitDepth;
if (offset == 8) {
mask = mask0;
offset = 0;
i--;
}
}
}
/**
* size original: samplesPerRow sizeFinal: samplesPerRowPacked (trailing
* elements are trash!)
**/
static void packInplaceByte(final ImageInfo iminfo, final byte[] src, final byte[] dst, final boolean scaled) {
final int bitDepth = iminfo.bitDepth;
if (bitDepth >= 8)
return; // nothing to do
final int mask0 = ImageLineHelper.getMaskForPackedFormatsLs(bitDepth);
final int scalefactor = 8 - bitDepth;
final int offset0 = 8 - bitDepth;
int v, v0;
int offset = 8 - bitDepth;
v0 = src[0]; // first value is special
dst[0] = 0;
if (scaled)
v0 >>= scalefactor;
v0 = ((v0 & mask0) << offset);
for (int i = 0, j = 0; j < iminfo.samplesPerRow; j++) {
v = src[j];
if (scaled)
v >>= scalefactor;
dst[i] |= ((v & mask0) << offset);
offset -= bitDepth;
if (offset < 0) {
offset = offset0;
i++;
dst[i] = 0;
}
}
dst[0] |= v0;
}
/**
* Creates a new ImageLine similar to this, but unpacked
*
* The caller must be sure that the original was really packed
*/
public ImageLine unpackToNewImageLine() {
final ImageLine newline = new ImageLine(imgInfo, sampleType, true);
if (sampleType == SampleType.INT)
unpackInplaceInt(imgInfo, scanline, newline.scanline, false);
else
unpackInplaceByte(imgInfo, scanlineb, newline.scanlineb, false);
return newline;
}
/**
* Creates a new ImageLine similar to this, but packed
*
* The caller must be sure that the original was really unpacked
*/
public ImageLine packToNewImageLine() {
final ImageLine newline = new ImageLine(imgInfo, sampleType, false);
if (sampleType == SampleType.INT)
packInplaceInt(imgInfo, scanline, newline.scanline, false);
else
packInplaceByte(imgInfo, scanlineb, newline.scanlineb, false);
return newline;
}
public FilterType getFilterUsed() {
return filterUsed;
}
public void setFilterUsed(final FilterType ft) {
filterUsed = ft;
}
public int[] getScanlineInt() {
return scanline;
}
public byte[] getScanlineByte() {
return scanlineb;
}
/**
* Basic info
*/
@Override
public String toString() {
return "row=" + rown + " cols=" + imgInfo.cols + " bpc=" + imgInfo.bitDepth + " size=" + scanline.length;
}
/**
* Prints some statistics - just for debugging
*/
public static void showLineInfo(final ImageLine line) {
System.out.println(line);
final ImageLineStats stats = new ImageLineHelper.ImageLineStats(line);
System.out.println(stats);
System.out.println(ImageLineHelper.infoFirstLastPixels(line));
}
}