package com.hphoto.image;
/**
* A class containing static math methods useful for image processing.
*/
public class ImageMath {
/**
* The value of pi as a float.
*/
public final static float PI = (float)Math.PI;
/**
* The value of half pi as a float.
*/
public final static float HALF_PI = (float)Math.PI/2.0f;
/**
* The value of quarter pi as a float.
*/
public final static float QUARTER_PI = (float)Math.PI/4.0f;
/**
* The value of two pi as a float.
*/
public final static float TWO_PI = (float)Math.PI*2.0f;
/**
* Apply a bias to a number in the unit interval, moving numbers towards 0 or 1
* according to the bias parameter.
* @param a the number to bias
* @param b the bias parameter. 0.5 means no change, smaller values bias towards 0, larger towards 1.
* @return the output value
*/
public static float bias(float a, float b) {
// return (float)Math.pow(a, Math.log(b) / Math.log(0.5));
return a/((1.0f/b-2)*(1.0f-a)+1);
}
/**
* A variant of the gamma function.
* @param a the number to apply gain to
* @param b the gain parameter. 0.5 means no change, smaller values reduce gain, larger values increase gain.
* @return the output value
*/
public static float gain(float a, float b) {
/*
float p = (float)Math.log(1.0 - b) / (float)Math.log(0.5);
if (a < .001)
return 0.0f;
else if (a > .999)
return 1.0f;
if (a < 0.5)
return (float)Math.pow(2 * a, p) / 2;
else
return 1.0f - (float)Math.pow(2 * (1. - a), p) / 2;
*/
float c = (1.0f/b-2.0f) * (1.0f-2.0f*a);
if (a < 0.5)
return a/(c+1.0f);
else
return (c-a)/(c-1.0f);
}
/**
* The step function. Returns 0 below a threshold, 1 above.
* @param a the threshold position
* @param x the input parameter
* @return the output value - 0 or 1
*/
public static float step(float a, float x) {
return (x < a) ? 0.0f : 1.0f;
}
/**
* The pulse function. Returns 1 between two thresholds, 0 outside.
* @param a the lower threshold position
* @param b the upper threshold position
* @param x the input parameter
* @return the output value - 0 or 1
*/
public static float pulse(float a, float b, float x) {
return (x < a || x >= b) ? 0.0f : 1.0f;
}
/**
* A smoothed pulse function. A cubic function is used to smooth the step between two thresholds.
* @param a1 the lower threshold position for the start of the pulse
* @param a2 the upper threshold position for the start of the pulse
* @param b1 the lower threshold position for the end of the pulse
* @param b2 the upper threshold position for the end of the pulse
* @param x the input parameter
* @return the output value
*/
public static float smoothPulse(float a1, float a2, float b1, float b2, float x) {
if (x < a1 || x >= b2)
return 0;
if (x >= a2) {
if (x < b1)
return 1.0f;
x = (x - b1) / (b2 - b1);
return 1.0f - (x*x * (3.0f - 2.0f*x));
}
x = (x - a1) / (a2 - a1);
return x*x * (3.0f - 2.0f*x);
}
/**
* A smoothed step function. A cubic function is used to smooth the step between two thresholds.
* @param a the lower threshold position
* @param b the upper threshold position
* @param x the input parameter
* @return the output value
*/
public static float smoothStep(float a, float b, float x) {
if (x < a)
return 0;
if (x >= b)
return 1;
x = (x - a) / (b - a);
return x*x * (3 - 2*x);
}
/**
* A "circle up" function. Returns y on a unit circle given 1-x. Useful for forming bevels.
* @param x the input parameter in the range 0..1
* @return the output value
*/
public static float circleUp(float x) {
x = 1-x;
return (float)Math.sqrt(1-x*x);
}
/**
* A "circle down" function. Returns 1-y on a unit circle given x. Useful for forming bevels.
* @param x the input parameter in the range 0..1
* @return the output value
*/
public static float circleDown(float x) {
return 1.0f-(float)Math.sqrt(1-x*x);
}
/**
* Clamp a value to an interval.
* @param a the lower clamp threshold
* @param b the upper clamp threshold
* @param x the input parameter
* @return the clamped value
*/
public static float clamp(float x, float a, float b) {
return (x < a) ? a : (x > b) ? b : x;
}
/**
* Clamp a value to an interval.
* @param a the lower clamp threshold
* @param b the upper clamp threshold
* @param x the input parameter
* @return the clamped value
*/
public static int clamp(int x, int a, int b) {
return (x < a) ? a : (x > b) ? b : x;
}
/**
* Return a mod b. This differs from the % operator with respect to negative numbers.
* @param a the dividend
* @param b the divisor
* @return a mod b
*/
public static double mod(double a, double b) {
int n = (int)(a/b);
a -= n*b;
if (a < 0)
return a + b;
return a;
}
/**
* Return a mod b. This differs from the % operator with respect to negative numbers.
* @param a the dividend
* @param b the divisor
* @return a mod b
*/
public static float mod(float a, float b) {
int n = (int)(a/b);
a -= n*b;
if (a < 0)
return a + b;
return a;
}
/**
* Return a mod b. This differs from the % operator with respect to negative numbers.
* @param a the dividend
* @param b the divisor
* @return a mod b
*/
public static int mod(int a, int b) {
int n = a/b;
a -= n*b;
if (a < 0)
return a + b;
return a;
}
/**
* The triangle function. Returns a repeating triangle shape in the range 0..1 with wavelength 1.0
* @param x the input parameter
* @return the output value
*/
public static float triangle(float x) {
float r = mod(x, 1.0f);
return 2.0f*(r < 0.5 ? r : 1-r);
}
/**
* Linear interpolation.
* @param t the interpolation parameter
* @param a the lower interpolation range
* @param b the upper interpolation range
* @return the interpolated value
*/
public static float lerp(float t, float a, float b) {
return a + t * (b - a);
}
/**
* Linear interpolation.
* @param t the interpolation parameter
* @param a the lower interpolation range
* @param b the upper interpolation range
* @return the interpolated value
*/
public static int lerp(float t, int a, int b) {
return (int)(a + t * (b - a));
}
/**
* Linear interpolation of ARGB values.
* @param t the interpolation parameter
* @param rgb1 the lower interpolation range
* @param rgb2 the upper interpolation range
* @return the interpolated value
*/
public static int mixColors(float t, int rgb1, int rgb2) {
int a1 = (rgb1 >> 24) & 0xff;
int r1 = (rgb1 >> 16) & 0xff;
int g1 = (rgb1 >> 8) & 0xff;
int b1 = rgb1 & 0xff;
int a2 = (rgb2 >> 24) & 0xff;
int r2 = (rgb2 >> 16) & 0xff;
int g2 = (rgb2 >> 8) & 0xff;
int b2 = rgb2 & 0xff;
a1 = lerp(t, a1, a2);
r1 = lerp(t, r1, r2);
g1 = lerp(t, g1, g2);
b1 = lerp(t, b1, b2);
return (a1 << 24) | (r1 << 16) | (g1 << 8) | b1;
}
/**
* Bilinear interpolation of ARGB values.
* @param x the X interpolation parameter 0..1
* @param y the y interpolation parameter 0..1
* @param rgb array of four ARGB values in the order NW, NE, SW, SE
* @return the interpolated value
*/
public static int bilinearInterpolate(float x, float y, int nw, int ne, int sw, int se) {
float m0, m1;
int a0 = (nw >> 24) & 0xff;
int r0 = (nw >> 16) & 0xff;
int g0 = (nw >> 8) & 0xff;
int b0 = nw & 0xff;
int a1 = (ne >> 24) & 0xff;
int r1 = (ne >> 16) & 0xff;
int g1 = (ne >> 8) & 0xff;
int b1 = ne & 0xff;
int a2 = (sw >> 24) & 0xff;
int r2 = (sw >> 16) & 0xff;
int g2 = (sw >> 8) & 0xff;
int b2 = sw & 0xff;
int a3 = (se >> 24) & 0xff;
int r3 = (se >> 16) & 0xff;
int g3 = (se >> 8) & 0xff;
int b3 = se & 0xff;
float cx = 1.0f-x;
float cy = 1.0f-y;
m0 = cx * a0 + x * a1;
m1 = cx * a2 + x * a3;
int a = (int)(cy * m0 + y * m1);
m0 = cx * r0 + x * r1;
m1 = cx * r2 + x * r3;
int r = (int)(cy * m0 + y * m1);
m0 = cx * g0 + x * g1;
m1 = cx * g2 + x * g3;
int g = (int)(cy * m0 + y * m1);
m0 = cx * b0 + x * b1;
m1 = cx * b2 + x * b3;
int b = (int)(cy * m0 + y * m1);
return (a << 24) | (r << 16) | (g << 8) | b;
}
/**
* Return the NTSC gray level of an RGB value.
* @param rgb1 the input pixel
* @return the gray level (0-255)
*/
public static int brightnessNTSC(int rgb) {
int r = (rgb >> 16) & 0xff;
int g = (rgb >> 8) & 0xff;
int b = rgb & 0xff;
return (int)(r*0.299f + g*0.587f + b*0.114f);
}
// Catmull-Rom splines
private final static float m00 = -0.5f;
private final static float m01 = 1.5f;
private final static float m02 = -1.5f;
private final static float m03 = 0.5f;
private final static float m10 = 1.0f;
private final static float m11 = -2.5f;
private final static float m12 = 2.0f;
private final static float m13 = -0.5f;
private final static float m20 = -0.5f;
private final static float m21 = 0.0f;
private final static float m22 = 0.5f;
private final static float m23 = 0.0f;
private final static float m30 = 0.0f;
private final static float m31 = 1.0f;
private final static float m32 = 0.0f;
private final static float m33 = 0.0f;
/**
* Compute a Catmull-Rom spline.
* @param x the input parameter
* @param numKnots the number of knots in the spline
* @param knots the array of knots
* @return the spline value
*/
public static float spline(float x, int numKnots, float[] knots) {
int span;
int numSpans = numKnots - 3;
float k0, k1, k2, k3;
float c0, c1, c2, c3;
if (numSpans < 1)
throw new IllegalArgumentException("Too few knots in spline");
x = clamp(x, 0, 1) * numSpans;
span = (int)x;
if (span > numKnots-4)
span = numKnots-4;
x -= span;
k0 = knots[span];
k1 = knots[span+1];
k2 = knots[span+2];
k3 = knots[span+3];
c3 = m00*k0 + m01*k1 + m02*k2 + m03*k3;
c2 = m10*k0 + m11*k1 + m12*k2 + m13*k3;
c1 = m20*k0 + m21*k1 + m22*k2 + m23*k3;
c0 = m30*k0 + m31*k1 + m32*k2 + m33*k3;
return ((c3*x + c2)*x + c1)*x + c0;
}
/**
* Compute a Catmull-Rom spline, but with variable knot spacing.
* @param x the input parameter
* @param numKnots the number of knots in the spline
* @param xknots the array of knot x values
* @param yknots the array of knot y values
* @return the spline value
*/
public static float spline(float x, int numKnots, int[] xknots, int[] yknots) {
int span;
int numSpans = numKnots - 3;
float k0, k1, k2, k3;
float c0, c1, c2, c3;
if (numSpans < 1)
throw new IllegalArgumentException("Too few knots in spline");
for (span = 0; span < numSpans; span++)
if (xknots[span+1] > x)
break;
if (span > numKnots-3)
span = numKnots-3;
float t = (float)(x-xknots[span]) / (xknots[span+1]-xknots[span]);
span--;
if (span < 0) {
span = 0;
t = 0;
}
k0 = yknots[span];
k1 = yknots[span+1];
k2 = yknots[span+2];
k3 = yknots[span+3];
c3 = m00*k0 + m01*k1 + m02*k2 + m03*k3;
c2 = m10*k0 + m11*k1 + m12*k2 + m13*k3;
c1 = m20*k0 + m21*k1 + m22*k2 + m23*k3;
c0 = m30*k0 + m31*k1 + m32*k2 + m33*k3;
return ((c3*t + c2)*t + c1)*t + c0;
}
/**
* Compute a Catmull-Rom spline for RGB values.
* @param x the input parameter
* @param numKnots the number of knots in the spline
* @param knots the array of knots
* @return the spline value
*/
public static int colorSpline(float x, int numKnots, int[] knots) {
int span;
int numSpans = numKnots - 3;
float k0, k1, k2, k3;
float c0, c1, c2, c3;
if (numSpans < 1)
throw new IllegalArgumentException("Too few knots in spline");
x = clamp(x, 0, 1) * numSpans;
span = (int)x;
if (span > numKnots-4)
span = numKnots-4;
x -= span;
int v = 0;
for (int i = 0; i < 4; i++) {
int shift = i * 8;
k0 = (knots[span] >> shift) & 0xff;
k1 = (knots[span+1] >> shift) & 0xff;
k2 = (knots[span+2] >> shift) & 0xff;
k3 = (knots[span+3] >> shift) & 0xff;
c3 = m00*k0 + m01*k1 + m02*k2 + m03*k3;
c2 = m10*k0 + m11*k1 + m12*k2 + m13*k3;
c1 = m20*k0 + m21*k1 + m22*k2 + m23*k3;
c0 = m30*k0 + m31*k1 + m32*k2 + m33*k3;
int n = (int)(((c3*x + c2)*x + c1)*x + c0);
if (n < 0)
n = 0;
else if (n > 255)
n = 255;
v |= n << shift;
}
return v;
}
/**
* Compute a Catmull-Rom spline for RGB values, but with variable knot spacing.
* @param x the input parameter
* @param numKnots the number of knots in the spline
* @param xknots the array of knot x values
* @param yknots the array of knot y values
* @return the spline value
*/
public static int colorSpline(int x, int numKnots, int[] xknots, int[] yknots) {
int span;
int numSpans = numKnots - 3;
float k0, k1, k2, k3;
float c0, c1, c2, c3;
if (numSpans < 1)
throw new IllegalArgumentException("Too few knots in spline");
for (span = 0; span < numSpans; span++)
if (xknots[span+1] > x)
break;
if (span > numKnots-3)
span = numKnots-3;
float t = (float)(x-xknots[span]) / (xknots[span+1]-xknots[span]);
span--;
if (span < 0) {
span = 0;
t = 0;
}
int v = 0;
for (int i = 0; i < 4; i++) {
int shift = i * 8;
k0 = (yknots[span] >> shift) & 0xff;
k1 = (yknots[span+1] >> shift) & 0xff;
k2 = (yknots[span+2] >> shift) & 0xff;
k3 = (yknots[span+3] >> shift) & 0xff;
c3 = m00*k0 + m01*k1 + m02*k2 + m03*k3;
c2 = m10*k0 + m11*k1 + m12*k2 + m13*k3;
c1 = m20*k0 + m21*k1 + m22*k2 + m23*k3;
c0 = m30*k0 + m31*k1 + m32*k2 + m33*k3;
int n = (int)(((c3*t + c2)*t + c1)*t + c0);
if (n < 0)
n = 0;
else if (n > 255)
n = 255;
v |= n << shift;
}
return v;
}
/**
* An implementation of Fant's resampling algorithm.
* @param source the source pixels
* @param dest the destination pixels
* @param length the length of the scanline to resample
* @param offset the start offset into the arrays
* @param stride the offset between pixels in consecutive rows
* @param out an array of output positions for each pixel
*/
public static void resample(int[] source, int[] dest, int length, int offset, int stride, float[] out) {
int i, j;
float sizfac;
float inSegment;
float outSegment;
int a, r, g, b, nextA, nextR, nextG, nextB;
float aSum, rSum, gSum, bSum;
float[] in;
int srcIndex = offset;
int destIndex = offset;
int lastIndex = source.length;
int rgb;
in = new float[length+2];
i = 0;
for (j = 0; j < length; j++) {
while (out[i+1] < j)
i++;
in[j] = i + (float) (j - out[i]) / (out[i + 1] - out[i]);
// in[j] = ImageMath.clamp( in[j], 0, length-1 );
}
in[length] = length;
in[length+1] = length;
inSegment = 1.0f;
outSegment = in[1];
sizfac = outSegment;
aSum = rSum = gSum = bSum = 0.0f;
rgb = source[srcIndex];
a = (rgb >> 24) & 0xff;
r = (rgb >> 16) & 0xff;
g = (rgb >> 8) & 0xff;
b = rgb & 0xff;
srcIndex += stride;
rgb = source[srcIndex];
nextA = (rgb >> 24) & 0xff;
nextR = (rgb >> 16) & 0xff;
nextG = (rgb >> 8) & 0xff;
nextB = rgb & 0xff;
srcIndex += stride;
i = 1;
while (i <= length) {
float aIntensity = inSegment * a + (1.0f - inSegment) * nextA;
float rIntensity = inSegment * r + (1.0f - inSegment) * nextR;
float gIntensity = inSegment * g + (1.0f - inSegment) * nextG;
float bIntensity = inSegment * b + (1.0f - inSegment) * nextB;
if (inSegment < outSegment) {
aSum += (aIntensity * inSegment);
rSum += (rIntensity * inSegment);
gSum += (gIntensity * inSegment);
bSum += (bIntensity * inSegment);
outSegment -= inSegment;
inSegment = 1.0f;
a = nextA;
r = nextR;
g = nextG;
b = nextB;
if (srcIndex < lastIndex)
rgb = source[srcIndex];
nextA = (rgb >> 24) & 0xff;
nextR = (rgb >> 16) & 0xff;
nextG = (rgb >> 8) & 0xff;
nextB = rgb & 0xff;
srcIndex += stride;
} else {
aSum += (aIntensity * outSegment);
rSum += (rIntensity * outSegment);
gSum += (gIntensity * outSegment);
bSum += (bIntensity * outSegment);
dest[destIndex] =
((int)Math.min(aSum/sizfac, 255) << 24) |
((int)Math.min(rSum/sizfac, 255) << 16) |
((int)Math.min(gSum/sizfac, 255) << 8) |
(int)Math.min(bSum/sizfac, 255);
destIndex += stride;
aSum = rSum = gSum = bSum = 0.0f;
inSegment -= outSegment;
outSegment = in[i+1] - in[i];
sizfac = outSegment;
i++;
}
}
}
/**
* Premultiply a block of pixels
*/
public static void premultiply( int[] p, int offset, int length ) {
length += offset;
for ( int i = offset; i < length; i ++ ) {
int rgb = p[i];
int a = (rgb >> 24) & 0xff;
int r = (rgb >> 16) & 0xff;
int g = (rgb >> 8) & 0xff;
int b = rgb & 0xff;
float f = a * (1.0f / 255.0f);
r *= f;
g *= f;
b *= f;
p[i] = (a << 24) | (r << 16) | (g << 8) | b;
}
}
/**
* Premultiply a block of pixels
*/
public static void unpremultiply( int[] p, int offset, int length ) {
length += offset;
for ( int i = offset; i < length; i ++ ) {
int rgb = p[i];
int a = (rgb >> 24) & 0xff;
int r = (rgb >> 16) & 0xff;
int g = (rgb >> 8) & 0xff;
int b = rgb & 0xff;
if ( a != 0 && a != 255 ) {
float f = 255.0f / a;
r *= f;
g *= f;
b *= f;
if ( r > 255 )
r = 255;
if ( g > 255 )
g = 255;
if ( b > 255 )
b = 255;
p[i] = (a << 24) | (r << 16) | (g << 8) | b;
}
}
}
}