package jogamp.opengl.util.pngj; import jogamp.opengl.util.pngj.ImageLineHelper.ImageLineStats; /** * Lightweight wrapper for an image scanline, used for read and write. * <p> * This object can be (usually it is) reused while iterating over the image * lines. * <p> * See <code>scanline</code> 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). * <p> * Except for 'packed' formats (gray/indexed with 1-2-4 bitdepth) each * <code>int</code> is a "sample" (one for channel), (0-255 or 0-65535) in * the corresponding PNG sequence: <code>R G B R G B...</code> or * <code>R G B A R G B A...</tt> * or <code>g g g ...</code> or <code>i i i</code> (palette index) * <p> * For bitdepth=1/2/4 , and if samplesUnpacked=false, each value is a PACKED * byte! * <p> * To convert a indexed line to RGB balues, see * <code>ImageLineHelper.palIdx2RGB()</code> (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 <code>scale==true<code>, 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 <code>scale==true<code>, 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)); } }