package org.hipi.image;
import org.apache.hadoop.io.BinaryComparable;
import org.apache.hadoop.io.WritableComparable;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
/**
* The header information for a HipiImage. HipiImageHeader encapsulates universally available
* information about a 2D image: width, height, storage format, color space, number of color bands
* (also called "channels") along with optional image meta data and EXIF data represented
* as key/value String dictionaries.
*
* The {@link org.hipi.image.io} package provides classes for reading (decoding) HipiImageHeader
* from both {@link org.hipi.imagebundle.HipiImageBundle} files and various standard image storage
* foramts such as JPEG and PNG.
*
* Note that this class implements the {@link org.apache.hadoop.io.WritableComparable} interface,
* allowing it to be used as a key/value object in MapReduce programs.
*/
public class HipiImageHeader implements WritableComparable<HipiImageHeader> {
/**
* Enumeration of the image storage formats supported in HIPI (e.g, JPEG, PNG, etc.).
*/
public enum HipiImageFormat {
UNDEFINED(0x0), JPEG(0x1), PNG(0x2), PPM(0x3);
private int format;
/**
* Creates an ImageFormat from an int.
*
* @param format Integer representation of ImageFormat.
*/
HipiImageFormat(int format) {
this.format = format;
}
/**
* Creates an ImageFormat from an int.
*
* @param format Integer representation of ImageFormat.
*
* @return Associated ImageFormat.
*
* @throws IllegalArgumentException if the parameter value does not correspond to a valid
* HipiImageFormat.
*/
public static HipiImageFormat fromInteger(int format) throws IllegalArgumentException {
for (HipiImageFormat fmt : values()) {
if (fmt.format == format) {
return fmt;
}
}
throw new IllegalArgumentException(String.format("There is no HipiImageFormat enum value " +
"associated with integer [%d]", format));
}
/**
* @return Integer representation of ImageFormat.
*/
public int toInteger() {
return format;
}
/**
* Default HipiImageFormat.
*
* @return HipiImageFormat.UNDEFINED
*/
public static HipiImageFormat getDefault() {
return UNDEFINED;
}
} // public enum ImageFormat
/**
* Enumeration of the color spaces supported in HIPI.
*/
public enum HipiColorSpace {
UNDEFINED(0x0), RGB(0x1), LUM(0x2);
private int cspace;
/**
* Creates a HipiColorSpace from an int
*
* @param format Integer representation of ColorSpace.
*/
HipiColorSpace(int cspace) {
this.cspace = cspace;
}
/**
* Creates a HipiColorSpace from an int.
*
* @param cspace Integer representation of ColorSpace.
*
* @return Associated HipiColorSpace value.
*
* @throws IllegalArgumentException if parameter does not correspond to a valid HipiColorSpace.
*/
public static HipiColorSpace fromInteger(int cspace) throws IllegalArgumentException {
for (HipiColorSpace cs : values()) {
if (cs.cspace == cspace) {
return cs;
}
}
throw new IllegalArgumentException(String.format("There is no HipiColorSpace enum value " +
"with an associated integer value of %d", cspace));
}
/**
* Integer representation of ColorSpace.
*
* @return Integer representation of ColorSpace.
*/
public int toInteger() {
return cspace;
}
/**
* Default HipiColorSpace. Currently (linear) RGB.
*
* @return Default ColorSpace enum value.
*/
public static HipiColorSpace getDefault() {
return RGB;
}
} // public enum ColorSpace
private HipiImageFormat storageFormat; // format used to store image on HDFS
private HipiColorSpace colorSpace; // color space of pixel data
private int width; // width of image
private int height; // height of image
private int bands; // number of color bands (aka channels)
/**
* A map containing key/value pairs of meta data associated with the
* image. These are (optionally) added during HIB construction and
* are distinct from the exif data that may be stored within the
* image file, which is accessed through the IIOMetadata object. For
* example, this would be the correct place to store the image tile
* offset and size if you were using a HIB to store a very large
* image as a collection of smaller image tiles. Another example
* would be using this dictionary to store the source url for an
* image downloaded from the Internet.
*/
private Map<String, String> metaData = new HashMap<String,String>();
/**
* EXIF data associated with the image represented as a
* HashMap. {@see hipi.image.io.ExifDataUtils}
*/
private Map<String, String> exifData = new HashMap<String,String>();
/**
* Creates an ImageHeader.
*/
public HipiImageHeader(HipiImageFormat storageFormat, HipiColorSpace colorSpace,
int width, int height,
int bands, byte[] metaDataBytes, Map<String,String> exifData)
throws IllegalArgumentException {
if (width < 1 || height < 1 || bands < 1) {
throw new IllegalArgumentException(String.format("Invalid spatial dimensions or number " +
"of bands: (%d,%d,%d)", width, height, bands));
}
this.storageFormat = storageFormat;
this.colorSpace = colorSpace;
this.width = width;
this.height = height;
this.bands = bands;
if (metaDataBytes != null) {
setMetaDataFromBytes(metaDataBytes);
}
this.exifData = exifData;
}
/**
* Creates an ImageHeader by calling #readFields on the data input
* object. Note that this function does not populate the exifData
* field. That must be done with a separate method call.
*/
public HipiImageHeader(DataInput input) throws IOException {
readFields(input);
}
/**
* Get the image storage type.
*
* @return Current image storage type.
*/
public HipiImageFormat getStorageFormat() {
return storageFormat;
}
/**
* Get the image color space.
*
* @return Image color space.
*/
public HipiColorSpace getColorSpace() {
return colorSpace;
}
/**
* Get width of image.
*
* @return Width of image.
*/
public int getWidth() {
return width;
}
/**
* Get height of image.
*
* @return Height of image.
*/
public int getHeight() {
return height;
}
/**
* Get number of color bands.
*
* @return Number of image bands.
*/
public int getNumBands() {
return bands;
}
/**
* Adds an metadata field to this header object. The information consists of a
* key-value pair where the key is an application-specific field name and the
* value is the corresponding information for that field.
*
* @param key
* the metadata field name
* @param value
* the metadata information
*/
public void addMetaData(String key, String value) {
metaData.put(key, value);
}
/**
* Sets the entire metadata map structure.
*
* @param metaData hash map containing the metadata key/value pairs
*/
public void setMetaData(HashMap<String, String> metaData) {
this.metaData = new HashMap<String, String>(metaData);
}
/**
* Attempt to retrieve metadata value associated with key.
*
* @param key field name of the desired metadata record
* @return either the value corresponding to the key or null if the
* key was not found
*/
public String getMetaData(String key) {
return metaData.get(key);
}
/**
* Get the entire list of all metadata that applications have
* associated with this image.
*
* @return a hash map containing the keys and values of the metadata
*/
public HashMap<String, String> getAllMetaData() {
return new HashMap<String, String>(metaData);
}
/**
* Create a binary representation of the application-specific
* metadata, ready to be serialized into a HIB file.
*
* @return A byte array containing the serialized hash map
*/
public byte[] getMetaDataAsBytes() {
try {
String jsonText = JSONValue.toJSONString(metaData);
final byte[] utf8Bytes = jsonText.getBytes("UTF-8");
return utf8Bytes;
} catch (java.io.UnsupportedEncodingException e) {
System.err.println("UTF-8 encoding exception in getMetaDataAsBytes()");
return null;
}
}
/**
* Recreates the general metadata from serialized bytes, usually
* from the beginning of a HIB file.
*
* @param utf8Bytes UTF-8-encoded bytes of a JSON object
* representing the data
*/
@SuppressWarnings("unchecked")
public void setMetaDataFromBytes(byte[] utf8Bytes) {
try {
String jsonText = new String(utf8Bytes, "UTF-8");
JSONObject jsonObject = (JSONObject)JSONValue.parse(jsonText);
metaData = (HashMap)jsonObject;
} catch (java.io.UnsupportedEncodingException e) {
System.err.println("UTF-8 encoding exception in setMetaDataAsBytes()");
}
}
/**
* Attempt to retrieve EXIF data value for specific key.
*
* @param key field name of the desired EXIF data record
* @return either the value corresponding to the key or null if the
* key was not found
*/
public String getExifData(String key) {
return exifData.get(key);
}
/**
* Get the entire map of EXIF data.
*
* @return a hash map containing the keys and values of the metadata
*/
public HashMap<String, String> getAllExifData() {
return new HashMap<String, String>(exifData);
}
/**
* Sets the entire EXIF data map structure.
*
* @param exifData hash map containing the EXIF data key/value pairs
*/
public void setExifData(HashMap<String, String> exifData) {
this.exifData = new HashMap<String, String>(exifData);
}
/**
* Sets the current object to be equal to another
* ImageHeader. Performs deep copy of meta data.
*
* @param header Target image header.
*/
public void set(HipiImageHeader header) {
this.storageFormat = header.getStorageFormat();
this.colorSpace = header.getColorSpace();
this.width = header.getWidth();
this.height = header.getHeight();
this.bands = header.getNumBands();
this.metaData = header.getAllMetaData();
this.exifData = header.getAllExifData();
}
/**
* Produce readable string representation of header.
* @see java.lang.Object#toString
*/
@Override
public String toString() {
String metaText = JSONValue.toJSONString(metaData);
return String.format("ImageHeader: (%d %d) %d x %d x %d meta: %s",
storageFormat.toInteger(), colorSpace.toInteger(), width, height, bands, metaText);
}
/**
* Serializes the HipiImageHeader object into a simple uncompressed binary format using the
* {@link java.io.DataOutput} interface.
*
* @see #readFields
* @see org.apache.hadoop.io.WritableComparable#write
*/
@Override
public void write(DataOutput out) throws IOException {
out.writeInt(storageFormat.toInteger());
out.writeInt(colorSpace.toInteger());
out.writeInt(width);
out.writeInt(height);
out.writeInt(bands);
byte[] metaDataBytes = getMetaDataAsBytes();
if (metaDataBytes == null || metaDataBytes.length == 0) {
out.writeInt(0);
} else {
out.writeInt(metaDataBytes.length);
out.write(metaDataBytes);
}
}
/**
* Deserializes HipiImageHeader object stored in a simple uncompressed binary format using the
* {@link java.io.DataInput} interface. The first twenty bytes are the image storage type,
* color space, width, height, and number of color bands (aka channels), all stored as ints,
* followed by the meta data stored as a set of key/value pairs in JSON UTF-8 format.
*
* @see org.apache.hadoop.io.WritableComparable#readFields
*/
@Override
public void readFields(DataInput input) throws IOException {
this.storageFormat = HipiImageFormat.fromInteger(input.readInt());
this.colorSpace = HipiColorSpace.fromInteger(input.readInt());
this.width = input.readInt();
this.height = input.readInt();
this.bands = input.readInt();
int len = input.readInt();
if (len > 0) {
byte[] metaDataBytes = new byte[len];
input.readFully(metaDataBytes, 0, len);
setMetaDataFromBytes(metaDataBytes);
}
}
/**
* Compare method inherited from the {@link java.lang.Comparable} interface. This method is
* currently incomplete and uses only the storage format to determine order.
*
* @param that another {@link HipiImageHeader} to compare with the current object
*
* @return An integer result of the comparison.
*
* @see java.lang.Comparable#compareTo
*/
@Override
public int compareTo(HipiImageHeader that) {
int thisFormat = this.storageFormat.toInteger();
int thatFormat = that.storageFormat.toInteger();
return (thisFormat < thatFormat ? -1 : (thisFormat == thatFormat ? 0 : 1));
}
/**
* Hash method inherited from the {@link java.lang.Object} base class. This method is
* currently incomplete and uses only the storage format to determine this hash.
*
* @return hash code for this object
*
* @see java.lang.Object#hashCode
*/
@Override
public int hashCode() {
return this.storageFormat.toInteger();
}
}