import java.awt.image.BufferedImage; /* * This source code has been placed into the public domain. */ /* * Note: This class only supports the non-standard construction of Haar wavelet * basis functions. There is currently no provision for standard basis functions. * For a good explanation see http://grail.cs.washington.edu/pub/stoll/wavelet1.pdf */ /** *

Instances of this class provide methods for applying a Haar wavelet filter to * two dimensional values stored in a byte array. It is primarily intended to be * used for image processing. It also provides methods for reversing the filter * and converting filters and data into images.

* *

Applying a filter to a byte array containing values in the range [0-255] * results in a same length integer array that contains the filter values; as * per the parameters set on the HaarFilter instance. A fractional integer * encoding is used for reasons of performance; it also ensures that filters * constructed with a sufficient number of fractional bits can be inverted * perfectly. As a consequence of the current implementation of this encoding * by this class, there is an upper limit to the size of value array that can be * filtered - presently 4096x4096.

* *

Filtering can only be performed on data arrays that have a size * which is a square of a power of two. At present, the class only supports * non-standard basis functions. NOTE: This implementation has not been * heavily tested and may contain any number of bugs or inadequacies, see NOTES * in the source for further information.

* * @author Tom Gibara */ public class HaarFilter { /** * The length of the side of the data to be filtered */ private final int size; /** * The square of the size. */ private final int sizeSqr; /** * A buffer that is allocated for performing filtering. */ private int[] buffer = null; /** * The number of filtering iterations performed. */ private int iterations; /** * The number of bits in the filter arrays processed. */ private int fractionalBits; /** * Constructs a new object for performing filtering. Individual filtering * instances are constructed to operate on data of a predetermined size. All * arrays passed to a filter must have a length which corresponds to the * size specified at construction (specifically size x size). * * @param size the length of the side of all squares of data/filter values */ public HaarFilter(int size) { this(size, -1, -1); } /** * Constructs a new object for performing filtering. Individual filtering * instances are constructed to operate on data of a predetermined size. All * arrays passed to a filter must have a length which corresponds to the * size specified at construction (specifically size x size). See * * @see #setIterations(int) for information about the iterations parameter * @see #setFractionalBits(int) for information about the fractionalBits parameter * * @param size the length of the side of all squares of data/filter values * @param iterations the number of iterations performed during filtering, or * -1 to use the default value * @param fractionalBits the number of binary fractional bits in the * number representation used in filters, or -1 to use the default * value */ public HaarFilter(int size, int iterations, int fractionalBits) { if (size <= 0) throw new IllegalArgumentException("size not strictly positive"); if (size != Integer.highestOneBit(size)) throw new IllegalArgumentException("size not a power of two"); if (size > 4096) throw new IllegalArgumentException("size too large to support int size arithmetic"); this.size = size; this.sizeSqr = size * size; setIterations(iterations); setFractionalBits(fractionalBits); } /** * The number of iterations that will be applied during a call to the * filter method. * * @see #setIterations(int) for more information * * @return the number of iterations performed during filtering, never negative */ public int getIterations() { return iterations; } /** * The number of fractional bits that will be used in the filtered values * returned by the filter method. * * @see #setFractionalBits(int) for more information * * @return the number of binary fractional bits in the number representation * used in filters, never negative */ public int getFractionalBits() { return fractionalBits; } /** * Specifies the number of times that the Haar wavelet is successively * applied during a call to the filter method. This cannot * exceed ln2(size). This maximum forms the sensible default value and * can be specified by passing a negative number to this method. * * @param the number of iterations performed during filtering, or -1 for the * default value */ public void setIterations(int iterations) { if (iterations < 0) iterations = Integer.numberOfTrailingZeros(size); if (1 << iterations > size) throw new IllegalArgumentException("too many iterations for size"); this.iterations = iterations; } /** * Specifies the number of bits in the integers returned by the * filter method that represent fractions of a whole number. * If fractionalBits is not zero, these bits will form the least * significant bits of the integers. Due to the size constraints that arise * from arithmetic on ints the number of fractional bits cannot * exceed 23. The sensible default value for the number of fractional bits * is twice the number of iterations; this default can be specified by * passing a negative number to this method. * * @param fractionalBits the number of binary fractional bits in the number * representation used in filters, or -1 for the default value */ public void setFractionalBits(int fractionalBits) { if (fractionalBits < 0) fractionalBits = iterations << 1; if (fractionalBits > 23) throw new IllegalArgumentException("fractionalBits exceeds 23, the largest storable in an int"); this.fractionalBits = fractionalBits; } /** * Filters the byte values supplied by applying the Haar wavelet for a * number of iterations and stores the result in the specified integer array. * If no filter array is supplied then a new array is created. The values * returned may have been scaled to accomodate fractional bits. * * @param values a byte array of length containing the values to be filtered, not null * @param filter an array which is to contain the filter values, or null * @return the array which contains the filter values */ public int[] filter(byte[] values, int[] filter) { //validate arguments if (values == null) throw new IllegalArgumentException("null values"); if (values.length != sizeSqr) throw new IllegalArgumentException("values array incorrect length"); if (filter == null) { filter = new int[sizeSqr]; } else { if (filter.length != sizeSqr) throw new IllegalArgumentException("filter array incorrect length"); } //lazily allocate a buffer if (buffer == null) buffer = new int[sizeSqr]; //convert values into integers for (int i = 0; i < filter.length; i++) { filter[i] = values[i] & 0xff; } //filter the values for (int i = 0; i < iterations; i++) { int[] s; int[] t; int length = size >> i; //horizontal processing s = filter; t = buffer; int hOffset = length >> 1; for (int y = 0; y < length; y++) { int sIndex, tIndex; tIndex = sIndex = y * size; for (int x = 0; x < length; x+=2) { int a = s[sIndex]; int b = s[sIndex + 1]; t[tIndex] = a + b; t[tIndex + hOffset] = a - b; sIndex += 2; tIndex ++; } } //vertical processing s = buffer; t = filter; int vOffset = (length >> 1) * size; for (int x = 0; x < length; x++) { int sIndex, tIndex; sIndex = tIndex = x; for (int y = 0; y < length; y += 2) { int a = s[sIndex]; int b = s[sIndex + size]; t[tIndex] = a + b; t[tIndex + vOffset] = a - b; sIndex += size << 1; tIndex += size; } } length <<= 1; } //normalize the number of fractional bits before returning normalize(filter); return filter; } /** * Converts a filter created by this object back into the supplied byte * array. If no byte array is supplied a new one is created. * * If the iterations and fractionalBits parameters * of this object have been modified since the filter was created then the * data will not be faithfully reconstructed. If the number of fractional * bits is less than its default value then the inversion may be imperfect * due to accumulated rounding errors. * * This method destroys the supplied filter array - this is done * intentionally for performance reasons. If you want to preserve your * filter clone it before passing it into this method. * * @param filter an array containing the filter values, not null * @param values an array which is to contain the recovered data, or null * @return the array which contains the recovered data */ /* * NOTE: As indicated, this implementation has not been thoroughly tested. * In particular, it might be possible that this method could overflow when * inverting large filters. I have not yet had the time to prove otherwise. */ public byte[] invert(int[] filter, byte[] values) { //validate arguments if (filter == null) throw new IllegalArgumentException("null filter"); if (filter.length != sizeSqr) throw new IllegalArgumentException("filter array incorrect length"); if (values == null) { values = new byte[sizeSqr]; } else { if (values.length != sizeSqr) throw new IllegalArgumentException("values array incorrect length"); } //lazily allocate a buffer if (buffer == null) buffer = new int[sizeSqr]; //reverse the filtering process for (int i = iterations - 1; i >= 0; i--) { int[] s; int[] t; int length = size >> i; //vertical processing s = filter; t = buffer; int vOffset = (length >> 1) * size; for (int x = 0; x < length; x++) { int sIndex, tIndex; sIndex = tIndex = x; for (int y = 0; y < length; y += 2) { int a = s[sIndex]; int b = s[sIndex + vOffset]; t[tIndex] = a + b; t[tIndex + size] = a - b; sIndex += size; tIndex += size << 1; } } //horizontal processing s = buffer; t = filter; int hOffset = length >> 1; for (int y = 0; y < length; y++) { int sIndex, tIndex; tIndex = sIndex = y * size; for (int x = 0; x < length; x+=2) { int a = s[sIndex]; int b = s[sIndex + hOffset]; t[tIndex] = a + b; t[tIndex + 1] = a - b; sIndex ++; tIndex += 2; } } length >>= 1; } //remove the fractional bits and convert to bytes for (int i = 0; i < values.length; i++) { int value = filter[i] >> fractionalBits; if (value > 255) value = 255; else if (value < 0) value = 0; values[i] = (byte) value; } return values; } /** * A utility method for converting filter values generated by this object * into an TYPE_BYTE_GRAY BufferedImage. If the * fractionalBits parameter of this object has been modified * since the filter was created then the image may not be accurately * generated. * * @param filter an array containing the filter values, not null * @return the generated image */ public BufferedImage filterToImage(int[] filter) { if (filter == null) throw new IllegalArgumentException("null filter"); if (filter.length != sizeSqr) throw new IllegalArgumentException("filter array incorrect length"); byte[] data = new byte[filter.length]; int shift = fractionalBits + 1; for (int i = 0; i < data.length; i++) { data[i] = (byte) (128 + (filter[i] >> shift)); } BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_BYTE_GRAY); image.getWritableTile(0, 0).setDataElements(0, 0, size, size, data); image.releaseWritableTile(0, 0); return image; } /** * A utility method for converting data values into an * TYPE_BYTE_GRAY BufferedImage. * * @param values an array containing the data values, not null * @return the generated image */ public BufferedImage valuesToImage(byte[] values) { if (values == null) throw new IllegalArgumentException("null values"); if (values.length != sizeSqr) throw new IllegalArgumentException("values array incorrect length"); BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_BYTE_GRAY); image.getWritableTile(0, 0).setDataElements(0, 0, size, size, values); image.releaseWritableTile(0, 0); return image; } /* * Adjusts for accumulated fractional bits in the raw filter array. */ private void normalize(int[] filter) { int shiftD = -2; int shiftY = (iterations << 1) - fractionalBits; int nextY = size >> (iterations-1); for (int y = 0; y < size; y++) { if (y == nextY) { shiftY += shiftD; nextY <<= 1; } int i = y * size; int nextX = nextY; int shiftX = shiftY; for (int x = 0; x < size; x++) { if (x == nextX) { shiftX += shiftD; nextX <<= 1; } if (shiftX > 0) { filter[i] >>= shiftX; } else if (shiftX < 0) { filter[i] <<= -shiftX; } i++; } } } }