Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
DeferredBufferedImage |
|
| 1.4;1.4 |
1 | /* $Id: DeferredBufferedImage.java 17820 2010-01-12 18:42:20Z linus $ | |
2 | ***************************************************************************** | |
3 | * Copyright (c) 2009 Contributors - see below | |
4 | * All rights reserved. This program and the accompanying materials | |
5 | * are made available under the terms of the Eclipse Public License v1.0 | |
6 | * which accompanies this distribution, and is available at | |
7 | * http://www.eclipse.org/legal/epl-v10.html | |
8 | * | |
9 | * Contributors: | |
10 | * tfmorris | |
11 | ***************************************************************************** | |
12 | * | |
13 | * Some portions of this file was previously release using the BSD License: | |
14 | */ | |
15 | ||
16 | // Copyright (c) 2008 The Regents of the University of California. All | |
17 | // Rights Reserved. Permission to use, copy, modify, and distribute this | |
18 | // software and its documentation without fee, and without a written | |
19 | // agreement is hereby granted, provided that the above copyright notice | |
20 | // and this paragraph appear in all copies. This software program and | |
21 | // documentation are copyrighted by The Regents of the University of | |
22 | // California. The software program and documentation are supplied "AS | |
23 | // IS", without any accompanying services from The Regents. The Regents | |
24 | // does not warrant that the operation of the program will be | |
25 | // uninterrupted or error-free. The end-user understands that the program | |
26 | // was developed for research purposes and is advised not to rely | |
27 | // exclusively on the program for any reason. IN NO EVENT SHALL THE | |
28 | // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, | |
29 | // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, | |
30 | // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF | |
31 | // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF | |
32 | // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY | |
33 | // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
34 | // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE | |
35 | // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF | |
36 | // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, | |
37 | // UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | |
38 | ||
39 | package org.argouml.gefext; | |
40 | ||
41 | import java.awt.AlphaComposite; | |
42 | import java.awt.Color; | |
43 | import java.awt.Composite; | |
44 | import java.awt.Graphics2D; | |
45 | import java.awt.Rectangle; | |
46 | import java.awt.image.BufferedImage; | |
47 | import java.awt.image.ColorModel; | |
48 | import java.awt.image.Raster; | |
49 | import java.awt.image.RenderedImage; | |
50 | import java.awt.image.SampleModel; | |
51 | import java.awt.image.WritableRaster; | |
52 | import java.util.Vector; | |
53 | ||
54 | import org.apache.log4j.Logger; | |
55 | import org.tigris.gef.base.Editor; | |
56 | ||
57 | /** | |
58 | * Rendered GEF image which uses a band buffer to minimize memory usage for | |
59 | * high resolution images. Image is rendered on demand in multiple passes | |
60 | * as getData is called. | |
61 | * <p> | |
62 | * NOTE: Only the methods which are called by ImageIO.write have been tested | |
63 | * and proven to work (getMinX/Y, getWidth, getHeight, & getData). This is | |
64 | * <em>not</em> a general purpose RenderedImage implementation, so you are on | |
65 | * your own with any others. | |
66 | * | |
67 | * @author Tom Morris <tfmorris@gmail.com> | |
68 | * @since ArgoUML 0.26 | |
69 | */ | |
70 | public class DeferredBufferedImage implements RenderedImage { | |
71 | ||
72 | 1 | private static final Logger LOG = |
73 | Logger.getLogger(DeferredBufferedImage.class); | |
74 | ||
75 | /** | |
76 | * RGB values for background color for image. Set as transparent. Chosen | |
77 | * because it's unlikely to be selected by the user, and leaves the diagram | |
78 | * readable if viewed without transparency. | |
79 | */ | |
80 | public static final int TRANSPARENT_BG_COLOR = 0x00efefef; | |
81 | ||
82 | /** | |
83 | * Background color | |
84 | */ | |
85 | 1 | public static final Color BACKGROUND_COLOR = |
86 | new Color(TRANSPARENT_BG_COLOR, true); | |
87 | ||
88 | private static final int BUFFER_HEIGHT = 32; | |
89 | ||
90 | private int x, y; | |
91 | private int width; | |
92 | private int height; | |
93 | private int scale; | |
94 | private BufferedImage image; | |
95 | private Editor editor; | |
96 | ||
97 | private int scaledBufferHeight; | |
98 | private int y1, y2; | |
99 | ||
100 | /** | |
101 | * Construct a new DeferredBufferedImage which will use GEF to render the | |
102 | * current diagram on demand. | |
103 | * | |
104 | * @param drawingArea bounding rectangle of the area to be drawn | |
105 | * @param imageType Type of image to be created (e.g. TYPE_INT_ARGB). Must | |
106 | * be on an image type which is supported by BufferedImage | |
107 | * @param ed GEF editor to be used for rendering the image | |
108 | * @param scaleFactor Integer scale factor to multiply by when rendering | |
109 | * image. | |
110 | */ | |
111 | public DeferredBufferedImage(Rectangle drawingArea, int imageType, | |
112 | 2 | Editor ed, int scaleFactor) { |
113 | ||
114 | 2 | editor = ed; |
115 | 2 | scale = scaleFactor; |
116 | ||
117 | 2 | x = drawingArea.x; |
118 | 2 | y = drawingArea.y; |
119 | 2 | width = drawingArea.width; |
120 | 2 | height = drawingArea.height; |
121 | ||
122 | // Scale everything up | |
123 | 2 | x = x * scale; |
124 | 2 | y = y * scale; |
125 | 2 | width = width * scale; |
126 | 2 | height = height * scale; |
127 | 2 | scaledBufferHeight = BUFFER_HEIGHT * scale; |
128 | ||
129 | // Create our bandbuffer which is just a small slice of the image | |
130 | // TODO: We used a fixed height buffer now, but we could be smarter and | |
131 | // compute a height which would fit in some memory budget, allowing us | |
132 | // to use taller buffers with narrower images, minimizing the overhead | |
133 | // of multiple rendering passes | |
134 | 2 | image = new BufferedImage(width, scaledBufferHeight, imageType); |
135 | ||
136 | // Initialize band buffer bounds | |
137 | 2 | y1 = y; |
138 | 2 | y2 = y1; |
139 | 2 | } |
140 | ||
141 | ||
142 | public Raster getData() { | |
143 | 0 | LOG.debug("getData with no params"); |
144 | 0 | return getData(new Rectangle(x, y, width, height)); |
145 | } | |
146 | ||
147 | public Raster getData(Rectangle clip) { | |
148 | // LOG.debug("getData Rectangle = " + clip); | |
149 | 160 | if (!isRasterValid(clip)) { |
150 | 6 | LOG.debug("Raster not valid, computing new raster"); |
151 | 6 | computeRaster(clip); |
152 | } | |
153 | 160 | Rectangle oClip = offsetWindow(clip); |
154 | 160 | Raster ras = image.getData(oClip); |
155 | 160 | Raster translatedRaster = ras.createTranslatedChild(clip.x, clip.y); |
156 | // LOG.debug("getData returning raster = " + translatedRaster); | |
157 | 160 | return translatedRaster; |
158 | } | |
159 | ||
160 | /** | |
161 | * @param clip clipping rectangle to test | |
162 | * @return true if clip rectangle is completely contained in image raster | |
163 | */ | |
164 | private boolean isRasterValid(Rectangle clip) { | |
165 | 160 | if (clip.height > scaledBufferHeight) { |
166 | 0 | throw new IndexOutOfBoundsException( |
167 | "clip rectangle must fit in band buffer"); | |
168 | } | |
169 | 160 | return (clip.y >= y1 && (clip.y + clip.height - 1) < y2); |
170 | } | |
171 | ||
172 | /** | |
173 | * Rasterize the next slice in the band buffer. Raster will be the full | |
174 | * width of the image and based vertically at the Y value of the current | |
175 | * clip rectangle. | |
176 | * | |
177 | * @param clip clip rectangle of interest | |
178 | */ | |
179 | private void computeRaster(Rectangle clip) { | |
180 | 6 | LOG.debug("Computing raster for rectangle " + clip); |
181 | ||
182 | // Create a new graphics context so we start with fresh transforms | |
183 | 6 | Graphics2D graphics = image.createGraphics(); |
184 | 6 | graphics.scale(1.0 * scale, 1.0 * scale); |
185 | ||
186 | // Fill with our background color | |
187 | 6 | graphics.setColor(BACKGROUND_COLOR); |
188 | 6 | Composite c = graphics.getComposite(); |
189 | 6 | graphics.setComposite(AlphaComposite.Src); |
190 | 6 | graphics.fillRect(0, 0, width, scaledBufferHeight); |
191 | 6 | graphics.setComposite(c); |
192 | ||
193 | // Translate & clip graphic to match region of interest | |
194 | 6 | graphics.setClip(0, 0, width, scaledBufferHeight); |
195 | 6 | graphics.translate(0, -clip.y / scale); |
196 | 6 | y1 = clip.y; |
197 | 6 | y2 = y1 + scaledBufferHeight; |
198 | ||
199 | // Ask GEF to print a band of the diagram (translated & clipped) | |
200 | 6 | editor.print(graphics); |
201 | ||
202 | // Make sure it isn't caching anything that should be written | |
203 | 6 | graphics.dispose(); |
204 | 6 | } |
205 | ||
206 | /** | |
207 | * Return a new clip rectangle which is offset by the base of our band | |
208 | * buffer. | |
209 | * | |
210 | * @param clip clipping rectangle | |
211 | * @return adjusted clipping rectangle | |
212 | */ | |
213 | private Rectangle offsetWindow(Rectangle clip) { | |
214 | 160 | int baseY = clip.y - y1; |
215 | 160 | return new Rectangle(clip.x, baseY, clip.width, |
216 | Math.min(clip.height, scaledBufferHeight - baseY)); | |
217 | } | |
218 | ||
219 | ||
220 | /** | |
221 | * Not implemented | |
222 | * | |
223 | * {@inheritDoc} | |
224 | * @see java.awt.image.RenderedImage#copyData(java.awt.image.WritableRaster) | |
225 | */ | |
226 | public WritableRaster copyData(WritableRaster outRaster) { | |
227 | 0 | throw new UnsupportedOperationException(); |
228 | // This needs to iterate to fill entire output raster if implemented | |
229 | // return image.copyData(outRaster); | |
230 | } | |
231 | ||
232 | /** | |
233 | * Not implemented. | |
234 | * {@inheritDoc} | |
235 | * | |
236 | * @see java.awt.image.RenderedImage#getSources() | |
237 | */ | |
238 | public Vector<RenderedImage> getSources() { | |
239 | 0 | return null; |
240 | } | |
241 | ||
242 | public ColorModel getColorModel() { | |
243 | 164 | return image.getColorModel(); |
244 | } | |
245 | ||
246 | public int getMinX() { | |
247 | 2 | LOG.debug("getMinX = 0"); |
248 | 2 | return 0; |
249 | } | |
250 | ||
251 | public int getMinY() { | |
252 | 2 | LOG.debug("getMinY = 0"); |
253 | 2 | return 0; |
254 | } | |
255 | ||
256 | public int getMinTileX() { | |
257 | 0 | LOG.debug("getMinTileX = 0"); |
258 | 0 | return 0; |
259 | } | |
260 | ||
261 | public int getMinTileY() { | |
262 | 0 | LOG.debug("getMinTileY = 0"); |
263 | 0 | return 0; |
264 | } | |
265 | ||
266 | public int getNumXTiles() { | |
267 | 0 | LOG.debug("getNumXTiles = 1"); |
268 | 0 | return 1; |
269 | } | |
270 | ||
271 | /** | |
272 | * Untested. Not guaranteed to work. | |
273 | * {@inheritDoc} | |
274 | * | |
275 | * @see java.awt.image.RenderedImage#getNumYTiles() | |
276 | */ | |
277 | public int getNumYTiles() { | |
278 | 0 | int tiles = (getHeight() + scaledBufferHeight - 1) / scaledBufferHeight; |
279 | 0 | LOG.debug("getNumYTiles = " + tiles); |
280 | 0 | return tiles; |
281 | } | |
282 | ||
283 | public Object getProperty(String name) { | |
284 | 0 | return image.getProperty(name); |
285 | } | |
286 | ||
287 | public String[] getPropertyNames() { | |
288 | 0 | return image.getPropertyNames(); |
289 | } | |
290 | ||
291 | public SampleModel getSampleModel() { | |
292 | 6 | return image.getSampleModel(); |
293 | } | |
294 | ||
295 | ||
296 | /** | |
297 | * Untested. Not guaranteed to work. | |
298 | * {@inheritDoc} | |
299 | * | |
300 | * @see java.awt.image.RenderedImage#getNumYTiles() | |
301 | */ | |
302 | ||
303 | public Raster getTile(int tileX, int tileY) { | |
304 | 0 | LOG.debug("getTile x=" + tileX + " y = " + tileY); |
305 | 0 | if (tileX < getMinTileX() |
306 | || tileX >= getMinTileX() + getNumXTiles() | |
307 | || tileY < getMinTileY() | |
308 | || tileY >= getMinTileY() + getNumYTiles()) { | |
309 | 0 | throw new IndexOutOfBoundsException(); |
310 | } | |
311 | // FIXME: Boundary condition at end of image for non-integral | |
312 | // multiples of BUFFER_HEIGHT | |
313 | 0 | Rectangle tileBounds = new Rectangle(0, (tileY - getMinTileY() |
314 | * scaledBufferHeight), width, scaledBufferHeight); | |
315 | 0 | return getData(tileBounds); |
316 | } | |
317 | ||
318 | public int getTileGridXOffset() { | |
319 | 0 | LOG.debug("getTileGridXOffset = 0"); |
320 | 0 | return 0; |
321 | } | |
322 | ||
323 | public int getTileGridYOffset() { | |
324 | 0 | LOG.debug("getTileGridYOffset = 0"); |
325 | 0 | return 0; |
326 | } | |
327 | ||
328 | public int getTileWidth() { | |
329 | 0 | LOG.debug("getTileWidth = " + width); |
330 | 0 | return width; |
331 | } | |
332 | ||
333 | public int getTileHeight() { | |
334 | 0 | LOG.debug("getTileHeight = " + scaledBufferHeight); |
335 | 0 | return scaledBufferHeight; |
336 | } | |
337 | ||
338 | public int getWidth() { | |
339 | 2 | LOG.debug("getWidth = " + width); |
340 | 2 | return width; |
341 | } | |
342 | ||
343 | public int getHeight() { | |
344 | 2 | LOG.debug("getHeight = " + height); |
345 | 2 | return height; |
346 | } | |
347 | } |