WarpPI/desktop/src/main/java/ar/com/hjg/pngj/IdatSet.java

251 lines
6.7 KiB
Java

package ar.com.hjg.pngj;
import java.util.Arrays;
import java.util.zip.Checksum;
import java.util.zip.Inflater;
/**
* This object process the concatenation of IDAT chunks.
* <p>
* It extends {@link DeflatedChunksSet}, adding the intelligence to unfilter
* rows, and to understand row lenghts in
* terms of ImageInfo and (eventually) Deinterlacer
*/
public class IdatSet extends DeflatedChunksSet {
protected byte rowUnfiltered[];
protected byte rowUnfilteredPrev[];
protected final ImageInfo imgInfo; // in the case of APNG this is the frame image
protected final Deinterlacer deinterlacer;
final RowInfo rowinfo; // info for the last processed row, for debug
protected int filterUseStat[] = new int[5]; // for stats
/**
* @param id
* Chunk id (first chunk), should be shared by all concatenated
* chunks
* @param iminfo
* Image info
* @param deinterlacer
* Not null if interlaced
*/
public IdatSet(String id, ImageInfo iminfo, Deinterlacer deinterlacer) {
this(id, iminfo, deinterlacer, null, null);
}
/**
* Special constructor with preallocated buffer.
* <p>
* <p>
* Same as {@link #IdatSet(String, ImageInfo, Deinterlacer)}, but you can
* pass a Inflater (will be reset internally),
* and a buffer (will be used only if size is enough)
*/
public IdatSet(String id, ImageInfo iminfo, Deinterlacer deinterlacer, Inflater inf, byte[] buffer) {
super(id, deinterlacer != null ? deinterlacer.getBytesToRead() + 1 : iminfo.bytesPerRow + 1, iminfo.bytesPerRow + 1, inf, buffer);
this.imgInfo = iminfo;
this.deinterlacer = deinterlacer;
this.rowinfo = new RowInfo(iminfo, deinterlacer);
}
/**
* Applies PNG un-filter to inflated raw line. Result in
* {@link #getUnfilteredRow()} {@link #getRowLen()}
*/
public void unfilterRow() {
unfilterRow(rowinfo.bytesRow);
}
// nbytes: NOT including the filter byte. leaves result in rowUnfiltered
protected void unfilterRow(int nbytes) {
if (rowUnfiltered == null || rowUnfiltered.length < row.length) {
rowUnfiltered = new byte[row.length];
rowUnfilteredPrev = new byte[row.length];
}
if (rowinfo.rowNsubImg == 0)
Arrays.fill(rowUnfiltered, (byte) 0); // see swap that follows
// swap
byte[] tmp = rowUnfiltered;
rowUnfiltered = rowUnfilteredPrev;
rowUnfilteredPrev = tmp;
int ftn = row[0];
if (!FilterType.isValidStandard(ftn))
throw new PngjInputException("Filter type " + ftn + " invalid");
FilterType ft = FilterType.getByVal(ftn);
filterUseStat[ftn]++;
rowUnfiltered[0] = row[0]; // we copy the filter type, can be useful
switch (ft) {
case FILTER_NONE:
unfilterRowNone(nbytes);
break;
case FILTER_SUB:
unfilterRowSub(nbytes);
break;
case FILTER_UP:
unfilterRowUp(nbytes);
break;
case FILTER_AVERAGE:
unfilterRowAverage(nbytes);
break;
case FILTER_PAETH:
unfilterRowPaeth(nbytes);
break;
default:
throw new PngjInputException("Filter type " + ftn + " not implemented");
}
}
private void unfilterRowAverage(final int nbytes) {
int i, j, x;
for (j = 1 - imgInfo.bytesPixel, i = 1; i <= nbytes; i++, j++) {
x = j > 0 ? (rowUnfiltered[j] & 0xff) : 0;
rowUnfiltered[i] = (byte) (row[i] + (x + (rowUnfilteredPrev[i] & 0xFF)) / 2);
}
}
private void unfilterRowNone(final int nbytes) {
for (int i = 1; i <= nbytes; i++) {
rowUnfiltered[i] = (byte) (row[i]);
}
}
private void unfilterRowPaeth(final int nbytes) {
int i, j, x, y;
for (j = 1 - imgInfo.bytesPixel, i = 1; i <= nbytes; i++, j++) {
x = j > 0 ? (rowUnfiltered[j] & 0xFF) : 0;
y = j > 0 ? (rowUnfilteredPrev[j] & 0xFF) : 0;
rowUnfiltered[i] = (byte) (row[i] + PngHelperInternal.filterPaethPredictor(x, rowUnfilteredPrev[i] & 0xFF, y));
}
}
private void unfilterRowSub(final int nbytes) {
int i, j;
for (i = 1; i <= imgInfo.bytesPixel; i++) {
rowUnfiltered[i] = (byte) (row[i]);
}
for (j = 1, i = imgInfo.bytesPixel + 1; i <= nbytes; i++, j++) {
rowUnfiltered[i] = (byte) (row[i] + rowUnfiltered[j]);
}
}
private void unfilterRowUp(final int nbytes) {
for (int i = 1; i <= nbytes; i++) {
rowUnfiltered[i] = (byte) (row[i] + rowUnfilteredPrev[i]);
}
}
/**
* does the unfiltering of the inflated row, and updates row info
*/
@Override
protected void preProcessRow() {
super.preProcessRow();
rowinfo.update(getRown());
unfilterRow();
rowinfo.updateBuf(rowUnfiltered, rowinfo.bytesRow + 1);
}
/**
* Method for async/callback mode .
* <p>
* In callback mode will be called as soon as each row is retrieved
* (inflated and unfiltered), after
* {@link #preProcessRow()}
* <p>
* This is a dummy implementation (this normally should be overriden) that
* does nothing more than compute the length
* of next row.
* <p>
* The return value is essential
* <p>
*
* @return Length of next row, in bytes (including filter byte),
* non-positive if done
*/
@Override
protected int processRowCallback() {
int bytesNextRow = advanceToNextRow();
return bytesNextRow;
}
@Override
protected void processDoneCallback() {
super.processDoneCallback();
}
/**
* Signals that we are done with the previous row, begin reading the next
* one.
* <p>
* In polled mode, calls setNextRowLen()
* <p>
* Warning: after calling this, the unfilterRow is invalid!
*
* @return Returns nextRowLen
*/
public int advanceToNextRow() {
// PngHelperInternal.LOGGER.info("advanceToNextRow");
int bytesNextRow;
if (deinterlacer == null) {
bytesNextRow = getRown() >= imgInfo.rows - 1 ? 0 : imgInfo.bytesPerRow + 1;
} else {
boolean more = deinterlacer.nextRow();
bytesNextRow = more ? deinterlacer.getBytesToRead() + 1 : 0;
}
if (!isCallbackMode()) { // in callback mode, setNextRowLen() is called internally
prepareForNextRow(bytesNextRow);
}
return bytesNextRow;
}
public boolean isRowReady() {
return !isWaitingForMoreInput();
}
/**
* Unfiltered row.
* <p>
* This should be called only if {@link #isRowReady()} returns true.
* <p>
* To get real length, use {@link #getRowLen()}
* <p>
*
* @return Unfiltered row, includes filter byte
*/
public byte[] getUnfilteredRow() {
return rowUnfiltered;
}
public Deinterlacer getDeinterlacer() {
return deinterlacer;
}
void updateCrcs(Checksum... idatCrcs) {
for (Checksum idatCrca : idatCrcs)
if (idatCrca != null)// just for testing
idatCrca.update(getUnfilteredRow(), 1, getRowFilled() - 1);
}
@Override
public void close() {
super.close();
rowUnfiltered = null;// not really necessary...
rowUnfilteredPrev = null;
}
/**
* Only for debug/stats
*
* @return Array of 5 integers (sum equal numbers of rows) counting each
* filter use
*/
public int[] getFilterUseStat() {
return filterUseStat;
}
}