// Copyright 2007 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); You may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
// applicable law or agreed to in writing, software distributed under the
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
// OF ANY KIND, either express or implied. See the License for the specific
// language governing permissions and limitations under the License.
package com.google.scrollview.ui;
import edu.umd.cs.piccolo.nodes.PImage;
import java.awt.image.BufferedImage;
import java.util.HashMap;
/**
* The ScrollViewImageHandler is a helper class which takes care of image
* processing. It is used to construct an Image from the message-stream and
* basically consists of a number of utility functions to process the input
* stream.
*
* @author wanke@google.com
*/
public class SVImageHandler {
/**
* Stores a mapping from the name of the string to its actual image. It
* enables us to re-use images without having to load or transmit them again
*/
static HashMap<String, PImage> images = new HashMap<String, PImage>();
/** A global flag stating whether we are currently expecting Image data */
static boolean readImageData = false;
// TODO(wanke) Consider moving all this into an SVImage class.
/** These are all values belonging to the image which is currently being read */
static String imageName = null; // Image name
static int bytesRead = 0; // Nr. of bytes already read
static int bpp = 0; // Bit depth
static int pictureArray[]; // The array holding the actual image
static int bytePerPixel = 0; // # of used bytes to transmit a pixel (32 bpp
// -> 7 BPP)
static int width = 0;
static int height = 0;
/* All methods are static, so we forbid to construct SVImageHandler objects */
private SVImageHandler() {
}
/**
* Takes a binary input string (consisting of '0' and '1' characters) and
* converts it to an integer representation usable as image data.
*/
private static int[] processBinaryImage(String inputLine) {
int BLACK = 0;
int WHITE = Integer.MAX_VALUE;
int[] imgData = new int[inputLine.length()];
for (int i = 0; i < inputLine.length(); i++) {
if (inputLine.charAt(i) == '0') {
imgData[i] = WHITE;
} else if (inputLine.charAt(i) == '1') {
imgData[i] = BLACK;
} // BLACK is default anyway
else { // Something is wrong: We did get unexpected data
System.out.println("Error: unexpected non-image-data: ("
+ SVImageHandler.bytesRead + "," + inputLine.length() + ","
+ (SVImageHandler.height * SVImageHandler.width) + ")");
System.exit(1);
}
}
return imgData;
}
/**
* Takes an input string with pixel depth of 8 (represented by 2 bytes in
* hexadecimal format, e.g. FF for white) and converts it to an
* integer representation usable as image data
*/
private static int[] processGrayImage(String inputLine) {
int[] imgData = new int[inputLine.length() / 2];
// Note: This is really inefficient, splitting it 2-byte-arrays in one pass
// would be wa faster than substring everytime.
for (int i = 0; i < inputLine.length(); i +=2) {
String s = inputLine.substring(i, i+1);
imgData[i] = Integer.parseInt(s, 16);
}
return imgData;
}
/**
* Takes an input string with pixel depth of 32 (represented by HTML-like
* colors in hexadecimal format, e.g. #00FF00 for green) and converts it to an
* integer representation usable as image data
*/
private static int[] process32bppImage(String inputLine) {
String[] strData = inputLine.split("#");
int[] imgData = new int[strData.length - 1];
for (int i = 1; i < strData.length; i++) {
imgData[i - 1] = Integer.parseInt(strData[i], 16);
}
return imgData;
}
/**
* Called when all image data is transmitted. Generates the actual image used
* by java and puts it into the images-hashmap.
*/
private static void closeImage() {
BufferedImage bi = null;
if (bpp == 1) {
bi = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY);
} else if (bpp == 8) {
bi = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
} else if (bpp == 32) {
bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
} else {
System.out.println("Unsupported Image Type: " + bpp + " bpp");
System.exit(1);
}
bi.setRGB(0, 0, width, height, pictureArray, 0, width);
PImage img = new PImage(bi);
images.put(imageName, img);
imageName = null;
readImageData = false;
System.out.println("(server, #Bytes:" + bytesRead + ") Image Completed");
bytesRead = 0;
bpp = 0;
}
/** Starts creation of a new image. */
public static void createImage(String name, int width, int height,
int bitsPerPixel) {
// Create buffered image that does not support transparency
bpp = bitsPerPixel;
if (bpp == 1) {
bytePerPixel = 1;
} else if (bpp == 8) {
bytePerPixel = 2;
} else if (bpp == 32) {
bytePerPixel = 7;
} else {
throw new IllegalArgumentException(
"bpp should be 1 (binary), 8 (gray) or 32 (argb), is " + bpp);
}
if (imageName != null) {
throw new IllegalArgumentException("Image " + imageName + " already opened!");
}
else {
imageName = name;
bytesRead = 0;
readImageData = true;
SVImageHandler.height = height;
SVImageHandler.width = width;
pictureArray = new int[width * height];
}
System.out.println("Processing Image with " + bpp + " bpp, size " + width + "x" + height);
}
/**
* Opens an Image from location. This means the image does not have to be
* actually transfered over the network. Thus, it is a lot faster than using
* the createImage method.
*
* @param location The (local) location from where to open the file. This is
* also the internal name associated with the image (if you want to draw it).
*/
public static void openImage(String location) {
PImage img = new PImage(location);
images.put(location, img);
}
/** Find the image corresponding to a given name */
public static PImage getImage(String name) {
return images.get(name);
}
/**
* Gets called while currently reading image data. Decides, how to process it
* (which image type, whether all data is there).
*/
public static void parseData(String inputLine) {
int[] data = null;
if (bpp == 1) {
data = processBinaryImage(inputLine);
} else if (bpp == 8) {
data = processGrayImage(inputLine);
} else if (bpp == 32) {
data = process32bppImage(inputLine);
} else {
System.out.println("Unsupported Bit Type: " + bpp);
}
System.arraycopy(data, 0, pictureArray, bytesRead, data.length);
bytesRead += data.length;
// We have read all image data - close the image
if (bytesRead == (height * width)) {
closeImage();
}
}
/** Returns whether we a currently reading image data or not */
public static boolean getReadImageData() {
return readImageData;
}
/** Computes how many bytes of the image data are still missing */
public static int getMissingRemainingBytes() {
return (height * width * bytePerPixel) - bytesRead;
}
}