204 lines
6.6 KiB
Java
204 lines
6.6 KiB
Java
package ar.com.hjg.pngj.pixels;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import ar.com.hjg.pngj.FilterType;
|
|
import ar.com.hjg.pngj.ImageInfo;
|
|
import ar.com.hjg.pngj.PngHelperInternal;
|
|
import ar.com.hjg.pngj.PngjExceptionInternal;
|
|
|
|
/** for use in adaptative strategy */
|
|
public class FiltersPerformance {
|
|
|
|
private final ImageInfo iminfo;
|
|
private double memoryA = 0.7; // empirical (not very critical: 0.72)
|
|
private int lastrow = -1;
|
|
private double[] absum = new double[5];// depending on the strategy not all values might be
|
|
// computed for all
|
|
private double[] entropy = new double[5];
|
|
private double[] cost = new double[5];
|
|
private int[] histog = new int[256]; // temporary, not normalized
|
|
private int lastprefered = -1;
|
|
private boolean initdone = false;
|
|
private double preferenceForNone = 1.0; // higher gives more preference to NONE
|
|
|
|
// this values are empirical (montecarlo), for RGB8 images with entropy estimator for NONE and
|
|
// memory=0.7
|
|
// DONT MODIFY THIS
|
|
public static final double[] FILTER_WEIGHTS_DEFAULT = {0.73, 1.03, 0.97, 1.11, 1.22}; // lower is
|
|
// better!
|
|
|
|
private double[] filter_weights = new double[] {-1, -1, -1, -1, -1};
|
|
|
|
private final static double LOG2NI = -1.0 / Math.log(2.0);
|
|
|
|
public FiltersPerformance(ImageInfo imgInfo) {
|
|
this.iminfo = imgInfo;
|
|
}
|
|
|
|
private void init() {
|
|
if (filter_weights[0] < 0) {// has not been set from outside
|
|
System.arraycopy(FILTER_WEIGHTS_DEFAULT, 0, filter_weights, 0, 5);
|
|
double wNone = filter_weights[0];
|
|
if (iminfo.bitDepth == 16)
|
|
wNone = 1.2;
|
|
else if (iminfo.alpha)
|
|
wNone = 0.8;
|
|
else if (iminfo.indexed || iminfo.bitDepth < 8)
|
|
wNone = 0.4; // we prefer NONE strongly
|
|
wNone /= preferenceForNone;
|
|
filter_weights[0] = wNone;
|
|
}
|
|
Arrays.fill(cost, 1.0);
|
|
initdone = true;
|
|
}
|
|
|
|
public void updateFromFiltered(FilterType ftype, byte[] rowff, int rown) {
|
|
updateFromRawOrFiltered(ftype, rowff, null, null, rown);
|
|
}
|
|
|
|
/** alternative: computes statistic without filtering */
|
|
public void updateFromRaw(FilterType ftype, byte[] rowb, byte[] rowbprev, int rown) {
|
|
updateFromRawOrFiltered(ftype, null, rowb, rowbprev, rown);
|
|
}
|
|
|
|
private void updateFromRawOrFiltered(FilterType ftype, byte[] rowff, byte[] rowb,
|
|
byte[] rowbprev, int rown) {
|
|
if (!initdone)
|
|
init();
|
|
if (rown != lastrow) {
|
|
Arrays.fill(absum, Double.NaN);
|
|
Arrays.fill(entropy, Double.NaN);
|
|
}
|
|
lastrow = rown;
|
|
if (rowff != null)
|
|
computeHistogram(rowff);
|
|
else
|
|
computeHistogramForFilter(ftype, rowb, rowbprev);
|
|
if (ftype == FilterType.FILTER_NONE)
|
|
entropy[ftype.val] = computeEntropyFromHistogram();
|
|
else
|
|
absum[ftype.val] = computeAbsFromHistogram();
|
|
}
|
|
|
|
/* WARNING: this is not idempotent, call it just once per cycle (sigh) */
|
|
public FilterType getPreferred() {
|
|
int fi = 0;
|
|
double vali = Double.MAX_VALUE, val = 0; // lower wins
|
|
for (int i = 0; i < 5; i++) {
|
|
if (!Double.isNaN(absum[i])) {
|
|
val = absum[i];
|
|
} else if (!Double.isNaN(entropy[i])) {
|
|
val = (Math.pow(2.0, entropy[i]) - 1.0) * 0.5;
|
|
} else
|
|
continue;
|
|
val *= filter_weights[i];
|
|
val = cost[i] * memoryA + (1 - memoryA) * val;
|
|
cost[i] = val;
|
|
if (val < vali) {
|
|
vali = val;
|
|
fi = i;
|
|
}
|
|
}
|
|
lastprefered = fi;
|
|
return FilterType.getByVal(lastprefered);
|
|
}
|
|
|
|
public final void computeHistogramForFilter(FilterType filterType, byte[] rowb, byte[] rowbprev) {
|
|
Arrays.fill(histog, 0);
|
|
int i, j, imax = iminfo.bytesPerRow;
|
|
switch (filterType) {
|
|
case FILTER_NONE:
|
|
for (i = 1; i <= imax; i++)
|
|
histog[rowb[i] & 0xFF]++;
|
|
break;
|
|
case FILTER_PAETH:
|
|
for (i = 1; i <= imax; i++)
|
|
histog[PngHelperInternal.filterRowPaeth(rowb[i], 0, rowbprev[i] & 0xFF, 0)]++;
|
|
for (j = 1, i = iminfo.bytesPixel + 1; i <= imax; i++, j++)
|
|
histog[PngHelperInternal.filterRowPaeth(rowb[i], rowb[j] & 0xFF, rowbprev[i] & 0xFF,
|
|
rowbprev[j] & 0xFF)]++;
|
|
break;
|
|
case FILTER_SUB:
|
|
for (i = 1; i <= iminfo.bytesPixel; i++)
|
|
histog[rowb[i] & 0xFF]++;
|
|
for (j = 1, i = iminfo.bytesPixel + 1; i <= imax; i++, j++)
|
|
histog[(rowb[i] - rowb[j]) & 0xFF]++;
|
|
break;
|
|
case FILTER_UP:
|
|
for (i = 1; i <= iminfo.bytesPerRow; i++)
|
|
histog[(rowb[i] - rowbprev[i]) & 0xFF]++;
|
|
break;
|
|
case FILTER_AVERAGE:
|
|
for (i = 1; i <= iminfo.bytesPixel; i++)
|
|
histog[((rowb[i] & 0xFF) - ((rowbprev[i] & 0xFF)) / 2) & 0xFF]++;
|
|
for (j = 1, i = iminfo.bytesPixel + 1; i <= imax; i++, j++)
|
|
histog[((rowb[i] & 0xFF) - ((rowbprev[i] & 0xFF) + (rowb[j] & 0xFF)) / 2) & 0xFF]++;
|
|
break;
|
|
default:
|
|
throw new PngjExceptionInternal("Bad filter:" + filterType);
|
|
}
|
|
}
|
|
|
|
public void computeHistogram(byte[] rowff) {
|
|
Arrays.fill(histog, 0);
|
|
for (int i = 1; i < iminfo.bytesPerRow; i++)
|
|
histog[rowff[i] & 0xFF]++;
|
|
}
|
|
|
|
public double computeAbsFromHistogram() {
|
|
int s = 0;
|
|
for (int i = 1; i < 128; i++)
|
|
s += histog[i] * i;
|
|
for (int i = 128, j = 128; j > 0; i++, j--)
|
|
s += histog[i] * j;
|
|
return s / (double) iminfo.bytesPerRow;
|
|
}
|
|
|
|
public final double computeEntropyFromHistogram() {
|
|
double s = 1.0 / iminfo.bytesPerRow;
|
|
double ls = Math.log(s);
|
|
|
|
double h = 0;
|
|
for (int x : histog) {
|
|
if (x > 0)
|
|
h += (Math.log(x) + ls) * x;
|
|
}
|
|
h *= s * LOG2NI;
|
|
if (h < 0.0)
|
|
h = 0.0;
|
|
return h;
|
|
}
|
|
|
|
/**
|
|
* If larger than 1.0, NONE will be more prefered. This must be called before init
|
|
*
|
|
* @param preferenceForNone around 1.0 (default: 1.0)
|
|
*/
|
|
public void setPreferenceForNone(double preferenceForNone) {
|
|
this.preferenceForNone = preferenceForNone;
|
|
}
|
|
|
|
/**
|
|
* Values greater than 1.0 (towards infinite) increase the memory towards 1. Values smaller than 1.0 (towards zero)
|
|
* decreases the memory .
|
|
*
|
|
*/
|
|
public void tuneMemory(double m) {
|
|
if (m == 0)
|
|
memoryA = 0.0;
|
|
else
|
|
memoryA = Math.pow(memoryA, 1.0 / m);
|
|
}
|
|
|
|
/**
|
|
* To set manually the filter weights. This is not recommended, unless you know what you are doing. Setting this
|
|
* ignores preferenceForNone and omits some heuristics
|
|
*
|
|
* @param weights Five doubles around 1.0, one for each filter type. Lower is preferered
|
|
*/
|
|
public void setFilterWeights(double[] weights) {
|
|
System.arraycopy(weights, 0, filter_weights, 0, 5);
|
|
}
|
|
}
|