blob: 4d0834365bcb864c91db1a222222af5c89e8d0fe [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.hadoop.chukwa.hicc;
import java.awt.Graphics;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.chukwa.util.ExceptionUtil;
public class ImageSlicer {
private BufferedImage src = null;
private Log log = LogFactory.getLog(ImageSlicer.class);
private String sandbox = System.getenv("CHUKWA_HOME")+File.separator+"webapps"+File.separator+"sandbox"+File.separator;
private int maxLevel = 0;
public ImageSlicer() {
}
/*
* Prepare a large image for tiling.
*
* Load an image from a file. Resize the image so that it is square,
* with dimensions that are an even power of two in length (e.g. 512,
* 1024, 2048, ...). Then, return it.
*
*/
public BufferedImage prepare(String filename) {
try {
src = ImageIO.read(new File(filename));
} catch (IOException e) {
log.error("Image file does not exist:"+filename+", can not render image.");
}
XYData fullSize = new XYData(1, 1);
while(fullSize.getX()<src.getWidth() || fullSize.getY()<src.getHeight()) {
fullSize.set(fullSize.getX()*2, fullSize.getY()*2);
}
float scaleX = (float)fullSize.getX()/src.getWidth();
float scaleY = (float)fullSize.getY()/src.getHeight();
log.info("Image size: ("+src.getWidth()+","+src.getHeight()+")");
log.info("Scale size: ("+scaleX+","+scaleY+")");
AffineTransform at =
AffineTransform.getScaleInstance(scaleX,scaleY);
// AffineTransform.getScaleInstance((fullSize.getX()-src.getWidth())/2,(fullSize.getY()-src.getHeight())/2);
AffineTransformOp op = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
BufferedImage dest = op.filter(src, null);
return dest;
}
/*
* Extract a single tile from a larger image.
*
* Given an image, a zoom level (int), a quadrant (column, row tuple;
* ints), and an output size, crop and size a portion of the larger
* image. If the given zoom level would result in scaling the image up,
* throw an error - no need to create information where none exists.
*
*/
public BufferedImage tile(BufferedImage image, int level, XYData quadrant, XYData size, boolean efficient) throws Exception {
double scale = Math.pow(2, level);
if(efficient) {
/* efficient: crop out the area of interest first, then scale and copy it */
XYData inverSize = new XYData((int)(image.getWidth(null)/(size.getX()*scale)),
(int)(image.getHeight(null)/(size.getY()*scale)));
XYData topLeft = new XYData(quadrant.getX()*size.getX()*inverSize.getX(),
quadrant.getY()*size.getY()*inverSize.getY());
XYData newSize = new XYData((size.getX()*inverSize.getX()),
(size.getY()*inverSize.getY()));
if(inverSize.getX()<1.0 || inverSize.getY() < 1.0) {
throw new Exception("Requested zoom level ("+level+") is too high.");
}
image = image.getSubimage(topLeft.getX(), topLeft.getY(), newSize.getX(), newSize.getY());
BufferedImage zoomed = new BufferedImage(size.getX(), size.getY(), BufferedImage.TYPE_INT_RGB);
zoomed.getGraphics().drawImage(image, 0, 0, size.getX(), size.getY(), null);
if(level>maxLevel) {
maxLevel = level;
}
return zoomed;
} else {
/* inefficient: copy the whole image, scale it and then crop out the area of interest */
XYData newSize = new XYData((int)(size.getX()*scale), (int)(size.getY()*scale));
XYData topLeft = new XYData(quadrant.getX()*size.getX(), quadrant.getY()*size.getY());
if(newSize.getX() > image.getWidth(null) || newSize.getY() > image.getHeight(null)) {
throw new Exception("Requested zoom level ("+level+") is too high.");
}
AffineTransform tx = new AffineTransform();
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
tx.scale(scale, scale);
image = op.filter(image, null);
BufferedImage zoomed = image.getSubimage(topLeft.getX(), topLeft.getY(), newSize.getX(), newSize.getY());
if(level>maxLevel) {
maxLevel = level;
}
return zoomed;
}
}
/*
* Recursively subdivide a large image into small tiles.
*
* Given an image, a zoom level (int), a quadrant (column, row tuple;
* ints), and an output size, cut the image into even quarters and
* recursively subdivide each, then generate a combined tile from the
* resulting subdivisions. If further subdivision would result in
* scaling the image up, use tile() to turn the image itself into a
* tile.
*/
public BufferedImage subdivide(BufferedImage image, int level, XYData quadrant, XYData size, String prefix) {
if(image.getWidth()<=size.getX()*Math.pow(2, level)) {
try {
BufferedImage outputImage = tile(image, level, quadrant, size, true);
write(outputImage, level, quadrant, prefix);
return outputImage;
} catch (Exception e) {
log.error(ExceptionUtil.getStackTrace(e));
}
}
BufferedImage zoomed = new BufferedImage(size.getX()*2, size.getY()*2, BufferedImage.TYPE_INT_RGB);
Graphics g = zoomed.getGraphics();
XYData newQuadrant = new XYData(quadrant.getX() * 2 + 0, quadrant.getY() * 2 + 0);
g.drawImage(subdivide(image, level+1, newQuadrant, size, prefix), 0, 0, null);
newQuadrant = new XYData(quadrant.getX()*2 + 0, quadrant.getY()*2 + 1);
g.drawImage(subdivide(image, level+1, newQuadrant, size, prefix), 0, size.getY(), null);
newQuadrant = new XYData(quadrant.getX()*2 + 1, quadrant.getY()*2 + 0);
g.drawImage(subdivide(image, level+1, newQuadrant, size, prefix), size.getX(), 0, null);
newQuadrant = new XYData(quadrant.getX()*2 + 1, quadrant.getY()*2 + 1);
g.drawImage(subdivide(image, level+1, newQuadrant, size, prefix), size.getX(), size.getY(), null);
BufferedImage outputImage = new BufferedImage(size.getX(), size.getY(), BufferedImage.TYPE_INT_RGB);
outputImage.getGraphics().drawImage(zoomed, 0, 0, size.getX(), size.getY(), null);
write(outputImage, level, quadrant, prefix);
return outputImage;
}
/*
* Write image file.
*/
public void write(BufferedImage image, int level, XYData quadrant, String prefix) {
StringBuilder outputFile = new StringBuilder();
outputFile.append(sandbox);
outputFile.append(File.separator);
outputFile.append(prefix);
outputFile.append("-");
outputFile.append(level);
outputFile.append("-");
outputFile.append(quadrant.getX());
outputFile.append("-");
outputFile.append(quadrant.getY());
outputFile.append(".png");
FileOutputStream fos;
try {
fos = new FileOutputStream(outputFile.toString());
ImageIO.write(image, "PNG", fos);
fos.close();
} catch (IOException e) {
log.error(ExceptionUtil.getStackTrace(e));
}
}
public int process(String filename) {
Pattern p = Pattern.compile("(.*)\\.(.*)");
Matcher m = p.matcher(filename);
if(m.matches()) {
String prefix = m.group(1);
String fullPath = sandbox + File.separator + filename;
subdivide(prepare(fullPath), 0, new XYData(0, 0), new XYData(256, 256), prefix);
return maxLevel;
}
return 0;
}
}
class XYData {
private int x = 0;
private int y = 0;
public XYData(int x, int y) {
this.x=x;
this.y=y;
}
public void set(int x, int y) {
this.x=x;
this.y=y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}