/* * 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.Component; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.awt.image.CropImageFilter; import java.awt.image.FilteredImageSource; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.List; 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; import org.apache.hadoop.chukwa.util.XssFilter; 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; } }