243 lines
7.0 KiB
Java
243 lines
7.0 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;
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|