package org.hipi.image;
import org.hipi.image.HipiImageHeader;
import org.hipi.image.HipiImageHeader.HipiImageFormat;
import org.hipi.image.HipiImageHeader.HipiColorSpace;
import org.hipi.image.RasterImage;
import org.hipi.image.PixelArrayFloat;
import org.hipi.util.ByteUtils;
import org.apache.hadoop.io.BinaryComparable;
import org.apache.hadoop.io.RawComparator;
import org.apache.hadoop.io.Writable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
* A raster image represented as an array of Java floats. A FloatImage consists
* of a flat array of pixel values represented as a {@link PixelArrayFloat}
* object along with a {@link HipiImageHeader} object.
*
* Note that individual pixel values in a FloatImage are understood to be in a linear
* color space. We suggest using the {@link PixelArray#setElemNonLinSRGB} method to
* set pixel values from 8-bit pixel values that are read from an image as these are
* usually represented in a non-linear gamma-compressed color space. The {@link PixelArrayFloat}
* class has routines for performing the conversion between linear and non-linear
* color spaces (e.g., sRGB and linear RGB).
*
* The {@link org.hipi.image.io} package provides classes for reading
* (decoding) and writing (encoding) FloatImage objects in various
* compressed and uncompressed image formats such as JPEG and PNG.
*/
public class FloatImage extends RasterImage {
public FloatImage() {
super((PixelArray)(new PixelArrayFloat()));
}
public FloatImage(int width, int height, int bands) throws IllegalArgumentException {
super((PixelArray)(new PixelArrayFloat()));
HipiImageHeader header = new HipiImageHeader(HipiImageFormat.UNDEFINED, HipiColorSpace.UNDEFINED, width, height, bands, null, null);
setHeader(header);
}
public FloatImage(int width, int height, int bands, HipiImageFormat imgFormat, HipiColorSpace colorspace) throws IllegalArgumentException {
super((PixelArray)(new PixelArrayFloat()));
HipiImageHeader header = new HipiImageHeader(imgFormat, colorspace,
width, height, bands, null, null);
setHeader(header);
}
public FloatImage(int width, int height, int bands, float[] data)
throws IllegalArgumentException {
super((PixelArray)(new PixelArrayFloat()));
HipiImageHeader header = new HipiImageHeader(HipiImageFormat.UNDEFINED, HipiColorSpace.UNDEFINED,
width, height, bands, null, null);
setHeader(header);
if (data == null || data.length != width*height*bands) {
throw new IllegalArgumentException("Size of data buffer does not match image dimensions.");
}
for (int i=0; i<width*height*bands; i++) {
pixelArray.setElemFloat(i,data[i]);
}
}
/**
* Get object type identifier.
*
* @return Type of object.
*/
public HipiImageType getType() {
return HipiImageType.FLOAT;
}
/**
* Provides direct access to underlying float array of pixel data.
*/
public float[] getData() {
return ((PixelArrayFloat)this.pixelArray).getData();
}
/**
* Compares two ByteImage objects for equality allowing for some
* amount of differences in pixel values.
*
* @return True if the two images have equal dimensions, color
* spaces, and are found to deviate by less than a maximum
* difference, false otherwise.
*/
public boolean equalsWithTolerance(RasterImage thatImage, float maxDifference) {
if (thatImage == null) {
return false;
}
// Verify dimensions in headers are equal
int w = this.getWidth();
int h = this.getHeight();
int b = this.getNumBands();
if (this.getColorSpace() != thatImage.getColorSpace() ||
thatImage.getWidth() != w || thatImage.getHeight() != h ||
thatImage.getNumBands() != b) {
return false;
}
// Get pointers to pixel arrays
PixelArray thisPA = this.getPixelArray();
PixelArray thatPA = thatImage.getPixelArray();
// Check that pixel data is equal.
for (int i=0; i<w*h*b; i++) {
double diff = Math.abs(thisPA.getElemFloat(i)-thatPA.getElemFloat(i));
if (diff > maxDifference) {
return false;
}
}
// Passed, declare equality
return true;
}
/**
* Compares two FloatImage objects for equality.
*
* @return True if the two images are found to deviate by less than
* 1.0/255.0 at each pixel and across each band, false otherwise.
*/
@Override
public boolean equals(Object that) {
// Check for pointer equivalence
if (this == that)
return true;
// Verify object types are equal
if (!(that instanceof FloatImage))
return false;
return equalsWithTolerance((FloatImage)that, 0.0f);
}
/**
* Performs in-place addition with another {@link FloatImage}.
*
* @param thatImage target image to add to current image
*
* @throws IllegalArgumentException if the image dimensions do not match
*/
public void add(FloatImage thatImage) throws IllegalArgumentException {
// Verify input
checkCompatibleInputImage(thatImage);
// Perform in-place addition
int w = this.getWidth();
int h = this.getHeight();
int b = this.getNumBands();
float[] thisData = this.getData();
float[] thatData = thatImage.getData();
for (int i=0; i<w*h*b; i++) {
thisData[i] += thatData[i];
}
}
/**
* Performs in-place addition of a scalar to each band of every pixel.
*
* @param number scalar value to add to each band of each pixel
*/
public void add(float number) {
int w = this.getWidth();
int h = this.getHeight();
int b = this.getNumBands();
float[] thisData = this.getData();
for (int i=0; i<w*h*b; i++) {
thisData[i] += number;
}
}
/**
* Performs in-place elementwise multiplication of {@link FloatImage} and the current image.
*
* @param thatImage target image to use for multiplication
*/
public void multiply(FloatImage thatImage) throws IllegalArgumentException {
// Verify input
checkCompatibleInputImage(thatImage);
// Perform in-place elementwise multiply
int w = this.getWidth();
int h = this.getHeight();
int b = this.getNumBands();
float[] thisData = this.getData();
float[] thatData = thatImage.getData();
for (int i=0; i<w*h*b; i++) {
thisData[i] *= thatData[i];
}
}
/**
* Performs in-place multiplication with scalar.
*
* @param value Scalar to multiply with each band of each pixel.
*/
public void scale(float value) {
int w = this.getWidth();
int h = this.getHeight();
int b = this.getNumBands();
float[] thisData = this.getData();
for (int i=0; i<w*h*b; i++) {
thisData[i] *= value;
}
}
/**
* Computes hash of float array of image pixel data.
*
* @return Hash of pixel data represented as a string.
*
* @see ByteUtils#asHex is used to compute the hash.
*/
@Override
public String hex() {
float[] pels = this.getData();
return ByteUtils.asHex(ByteUtils.floatArrayToByteArray(pels));
}
/**
* Helper routine that verifies two images have compatible
* dimensions for common operations (addition, elementwise
* multiplication, etc.)
*
* @param image RasterImage to check
*
* @throws IllegalArgumentException if the image do not have
* compatible dimensions. Otherwise has no effect.
*/
protected void checkCompatibleInputImage(FloatImage image) throws IllegalArgumentException {
if (image.getColorSpace() != this.getColorSpace() || image.getWidth() != this.getWidth() ||
image.getHeight() != this.getHeight() || image.getNumBands() != this.getNumBands()) {
throw new IllegalArgumentException("Color space and/or image dimensions do not match.");
}
}
} // public class FloatImage...