This project has retired. For details please refer to its Attic page.
ImageSlicer xref
View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.chukwa.hicc;
19  
20  import java.awt.Graphics;
21  import java.awt.geom.AffineTransform;
22  import java.awt.image.AffineTransformOp;
23  import java.awt.image.BufferedImage;
24  import java.io.File;
25  import java.io.FileOutputStream;
26  import java.io.IOException;
27  import java.util.regex.Matcher;
28  import java.util.regex.Pattern;
29  
30  import javax.imageio.ImageIO;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.hadoop.chukwa.util.ExceptionUtil;
35  
36  public class ImageSlicer {
37    private BufferedImage src = null;
38    private Log log = LogFactory.getLog(ImageSlicer.class);
39    private String sandbox = System.getenv("CHUKWA_HOME")+File.separator+"webapps"+File.separator+"sandbox"+File.separator;
40    private int maxLevel = 0;
41    
42    public ImageSlicer() {
43    }
44    
45    /*
46     * Prepare a large image for tiling.
47     * 
48     * Load an image from a file. Resize the image so that it is square,
49     * with dimensions that are an even power of two in length (e.g. 512,
50     * 1024, 2048, ...). Then, return it.
51     * 
52     */
53    public BufferedImage prepare(String filename) {
54      try {
55        src = ImageIO.read(new File(filename));
56      } catch (IOException e) {
57        log.error("Image file does not exist:"+filename+", can not render image.");
58      }
59      XYData fullSize = new XYData(1, 1);
60      while(fullSize.getX()<src.getWidth() || fullSize.getY()<src.getHeight()) {
61        fullSize.set(fullSize.getX()*2, fullSize.getY()*2);
62      }
63      float scaleX = (float)fullSize.getX()/src.getWidth();
64      float scaleY = (float)fullSize.getY()/src.getHeight();
65      log.info("Image size: ("+src.getWidth()+","+src.getHeight()+")");
66      log.info("Scale size: ("+scaleX+","+scaleY+")");
67      
68      AffineTransform at = 
69        AffineTransform.getScaleInstance(scaleX,scaleY);
70  
71        //       AffineTransform.getScaleInstance((fullSize.getX()-src.getWidth())/2,(fullSize.getY()-src.getHeight())/2);
72      AffineTransformOp op = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
73      BufferedImage dest = op.filter(src, null);
74      return dest;
75    }
76    
77    /*
78     * Extract a single tile from a larger image.
79     * 
80     * Given an image, a zoom level (int), a quadrant (column, row tuple;
81     * ints), and an output size, crop and size a portion of the larger
82     * image. If the given zoom level would result in scaling the image up,
83     * throw an error - no need to create information where none exists.
84     * 
85     */
86    public BufferedImage tile(BufferedImage image, int level, XYData quadrant, XYData size, boolean efficient) throws Exception {
87      double scale = Math.pow(2, level);
88      if(efficient) {
89        /* efficient: crop out the area of interest first, then scale and copy it */
90        XYData inverSize = new XYData((int)(image.getWidth(null)/(size.getX()*scale)), 
91            (int)(image.getHeight(null)/(size.getY()*scale)));
92        XYData topLeft = new XYData(quadrant.getX()*size.getX()*inverSize.getX(), 
93            quadrant.getY()*size.getY()*inverSize.getY());
94        XYData newSize = new XYData((size.getX()*inverSize.getX()), 
95            (size.getY()*inverSize.getY()));
96        if(inverSize.getX()<1.0 || inverSize.getY() < 1.0) {
97          throw new Exception("Requested zoom level ("+level+") is too high.");
98        }
99        image = image.getSubimage(topLeft.getX(), topLeft.getY(), newSize.getX(), newSize.getY());
100       BufferedImage zoomed = new BufferedImage(size.getX(), size.getY(), BufferedImage.TYPE_INT_RGB);
101       zoomed.getGraphics().drawImage(image, 0, 0, size.getX(), size.getY(), null);
102       if(level>maxLevel) {
103         maxLevel = level;
104       }
105       return zoomed;
106     } else {
107       /* inefficient: copy the whole image, scale it and then crop out the area of interest */
108       XYData newSize = new XYData((int)(size.getX()*scale), (int)(size.getY()*scale));
109       XYData topLeft = new XYData(quadrant.getX()*size.getX(), quadrant.getY()*size.getY());
110       if(newSize.getX() > image.getWidth(null) || newSize.getY() > image.getHeight(null)) {
111         throw new Exception("Requested zoom level ("+level+") is too high.");
112       }
113       AffineTransform tx = new AffineTransform();
114       AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
115       tx.scale(scale, scale);
116       image = op.filter(image, null);
117       BufferedImage zoomed = image.getSubimage(topLeft.getX(), topLeft.getY(), newSize.getX(), newSize.getY());
118       if(level>maxLevel) {
119         maxLevel = level;
120       }
121       return zoomed;
122     }
123   }
124   
125   /*
126    * Recursively subdivide a large image into small tiles.
127    * 
128    * Given an image, a zoom level (int), a quadrant (column, row tuple;
129    * ints), and an output size, cut the image into even quarters and
130    * recursively subdivide each, then generate a combined tile from the
131    * resulting subdivisions. If further subdivision would result in
132    * scaling the image up, use tile() to turn the image itself into a
133    * tile.
134    */
135   public BufferedImage subdivide(BufferedImage image, int level, XYData quadrant, XYData size, String prefix) {
136     if(image.getWidth()<=size.getX()*Math.pow(2, level)) {
137       try {
138         BufferedImage outputImage = tile(image, level, quadrant, size, true);
139         write(outputImage, level, quadrant, prefix);
140         return outputImage;
141       } catch (Exception e) {
142         log.error(ExceptionUtil.getStackTrace(e));
143       }
144     }
145     
146     BufferedImage zoomed = new BufferedImage(size.getX()*2, size.getY()*2, BufferedImage.TYPE_INT_RGB);
147     Graphics g = zoomed.getGraphics();
148     XYData newQuadrant = new XYData(quadrant.getX() * 2 + 0, quadrant.getY() * 2 + 0);
149     g.drawImage(subdivide(image, level+1, newQuadrant, size, prefix), 0, 0, null);
150     newQuadrant = new XYData(quadrant.getX()*2 + 0, quadrant.getY()*2 + 1);
151     g.drawImage(subdivide(image, level+1, newQuadrant, size, prefix), 0, size.getY(), null);
152     newQuadrant = new XYData(quadrant.getX()*2 + 1, quadrant.getY()*2 + 0);
153     g.drawImage(subdivide(image, level+1, newQuadrant, size, prefix), size.getX(), 0, null);
154     newQuadrant = new XYData(quadrant.getX()*2 + 1, quadrant.getY()*2 + 1);
155     g.drawImage(subdivide(image, level+1, newQuadrant, size, prefix), size.getX(), size.getY(), null);
156     BufferedImage outputImage = new BufferedImage(size.getX(), size.getY(), BufferedImage.TYPE_INT_RGB);
157     outputImage.getGraphics().drawImage(zoomed, 0, 0, size.getX(), size.getY(), null);
158     write(outputImage, level, quadrant, prefix);
159     return outputImage;    
160   }
161   
162   /*
163    * Write image file.
164    */
165   public void write(BufferedImage image, int level, XYData quadrant, String prefix) {
166     StringBuilder outputFile = new StringBuilder();
167     outputFile.append(sandbox);
168     outputFile.append(File.separator);
169     outputFile.append(prefix);
170     outputFile.append("-");
171     outputFile.append(level);
172     outputFile.append("-");
173     outputFile.append(quadrant.getX());
174     outputFile.append("-");
175     outputFile.append(quadrant.getY());
176     outputFile.append(".png");
177     FileOutputStream fos;
178     try {
179       fos = new FileOutputStream(outputFile.toString());
180       ImageIO.write(image, "PNG", fos);
181       fos.close();   
182     } catch (IOException e) {
183       log.error(ExceptionUtil.getStackTrace(e));
184     }
185   }
186   
187   public int process(String filename) {
188     Pattern p = Pattern.compile("(.*)\\.(.*)");
189     Matcher m = p.matcher(filename);
190     if(m.matches()) {
191       String prefix = m.group(1);
192       String fullPath = sandbox + File.separator + filename;
193       subdivide(prepare(fullPath), 0, new XYData(0, 0), new XYData(256, 256), prefix);
194       return maxLevel;
195     }
196     return 0;
197   }
198 
199 }
200 
201 class XYData {
202   private int x = 0;
203   private int y = 0;
204   
205   public XYData(int x, int y) {
206     this.x=x;
207     this.y=y;
208   }
209 
210   public void set(int x, int y) {
211     this.x=x;
212     this.y=y;
213   }
214   
215   public int getX() {
216     return x;
217   }
218 
219   public int getY() {
220     return y;
221   }
222   
223 }