EMMA Coverage Report (generated Sat Oct 08 11:41:37 CEST 2011)
[all classes][net.sf.jomic.tools]

COVERAGE SUMMARY FOR SOURCE FILE [ImageTools.java]

nameclass, %method, %block, %line, %
ImageTools.java100% (1/1)89%  (40/45)74%  (1490/2013)83%  (299.6/362)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ImageTools100% (1/1)89%  (40/45)74%  (1490/2013)83%  (299.6/362)
getBluredImage (RenderedImage, String): RenderedImage 0%   (0/1)0%   (0/75)0%   (0/13)
getNumBands (RenderedImage): int 0%   (0/1)0%   (0/13)0%   (0/2)
isImageFile (File): boolean 0%   (0/1)0%   (0/15)0%   (0/2)
isValidBlurMode (String): boolean 0%   (0/1)0%   (0/7)0%   (0/1)
isValidRotation (int): boolean 0%   (0/1)0%   (0/15)0%   (0/1)
getRotatedImage (RenderedImage, double): RenderedImage 100% (1/1)6%   (5/83)0%   (0.1/14)
assertIsValidScaleMode (String): void 100% (1/1)37%  (7/19)67%  (1.3/2)
assertIsValidRotation (int): void 100% (1/1)43%  (9/21)70%  (1.4/2)
getRotatedImage (RenderedImage, int): RenderedImage 100% (1/1)51%  (21/41)60%  (6.6/11)
getScaleToSqueeze (int, int, int, int, String): double 100% (1/1)55%  (74/135)80%  (14.3/18)
createBufferedImage (int, int): BufferedImage 100% (1/1)65%  (15/23)67%  (2/3)
getImageFormat (ImageInputStream): String 100% (1/1)69%  (33/48)89%  (10.7/12)
getImageDimension (ImageInputStream): Dimension 100% (1/1)70%  (39/56)92%  (12/13)
createColorBox (int, int, Color): BufferedImage 100% (1/1)74%  (49/66)86%  (10.4/12)
getImageFormat (File): String 100% (1/1)76%  (16/21)94%  (6.6/7)
isImageSuffix (String): boolean 100% (1/1)78%  (14/18)75%  (1.5/2)
getAsImageIcon (RenderedImage): ImageIcon 100% (1/1)79%  (15/19)88%  (3.5/4)
possiblyAddImageFormatCopy (String, String): void 100% (1/1)79%  (76/96)84%  (10.9/13)
getAsQuickBufferedImage (RenderedImage): BufferedImage 100% (1/1)80%  (35/44)88%  (8.8/10)
<static initializer> 100% (1/1)80%  (12/15)80%  (0.8/1)
createBrokenImage (int, int, Color, Color): RenderedImage 100% (1/1)82%  (96/117)90%  (17.9/20)
getSqueezed (RenderedImage, int, int, String): RenderedImage 100% (1/1)83%  (81/98)92%  (18.4/20)
createBusyImage (int, int, Color, Color): RenderedImage 100% (1/1)84%  (113/134)90%  (19/21)
createImageInputStream (File): ImageInputStream 100% (1/1)85%  (22/26)92%  (5.5/6)
getImageDimension (File): Dimension 100% (1/1)85%  (22/26)92%  (5.5/6)
isImageFile (String): boolean 100% (1/1)89%  (32/36)94%  (7.5/8)
fillColorValues (Color, ColorModel): double [] 100% (1/1)91%  (128/140)94%  (19.8/21)
ImageTools (): void 100% (1/1)93%  (336/361)98%  (59.6/61)
readImage (File): RenderedImage 100% (1/1)95%  (70/74)98%  (20.5/21)
isValidScaleMode (String): boolean 100% (1/1)95%  (21/22)95%  (1/1)
getAsBufferedImage (RenderedImage): BufferedImage 100% (1/1)100% (4/4)100% (1/1)
getDeltaRotation (int, int): int 100% (1/1)100% (18/18)100% (5/5)
getFixedRotation (int): int 100% (1/1)100% (17/17)100% (5/5)
getImageProviderMap (): Map 100% (1/1)100% (3/3)100% (1/1)
getImageReader (ImageInputStream): ImageReader 100% (1/1)100% (5/5)100% (1/1)
getLeftRotation (int): int 100% (1/1)100% (11/11)100% (3/3)
getPossibleBlurModes (): String [] 100% (1/1)100% (3/3)100% (1/1)
getRenderingHints (): RenderingHints 100% (1/1)100% (3/3)100% (1/1)
getRightRotation (int): int 100% (1/1)100% (11/11)100% (3/3)
getSqueezedDimension (int, int, int, int, String): Dimension 100% (1/1)100% (34/34)100% (5/5)
instance (): ImageTools 100% (1/1)100% (8/8)100% (3/3)
isCompressedImageFormat (String): boolean 100% (1/1)100% (11/11)100% (2/2)
isLandscape (Dimension): boolean 100% (1/1)100% (7/7)100% (1/1)
isLandscape (RenderedImage): boolean 100% (1/1)100% (7/7)100% (1/1)
isLandscape (int, int): boolean 100% (1/1)100% (7/7)100% (1/1)

1// Jomic - a viewer for comic book archives.
2// Copyright (C) 2004-2011 Thomas Aglassinger
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <http://www.gnu.org/licenses/>.
16package net.sf.jomic.tools;
17 
18import java.awt.BasicStroke;
19import java.awt.Color;
20import java.awt.Dimension;
21import java.awt.Graphics2D;
22import java.awt.RenderingHints;
23import java.awt.color.ColorSpace;
24import java.awt.geom.AffineTransform;
25import java.awt.geom.Ellipse2D;
26import java.awt.image.BufferedImage;
27import java.awt.image.BufferedImageOp;
28import java.awt.image.ColorModel;
29import java.awt.image.RenderedImage;
30import java.io.File;
31import java.io.FileNotFoundException;
32import java.io.IOException;
33import java.util.Arrays;
34import java.util.Collections;
35import java.util.Iterator;
36import java.util.LinkedList;
37import java.util.List;
38import java.util.Map;
39import java.util.NoSuchElementException;
40import java.util.TreeMap;
41 
42import javax.imageio.IIOException;
43import javax.imageio.ImageIO;
44import javax.imageio.ImageReadParam;
45import javax.imageio.ImageReader;
46import javax.imageio.ImageTypeSpecifier;
47import javax.imageio.spi.ImageReaderSpi;
48import javax.imageio.stream.ImageInputStream;
49import javax.swing.ImageIcon;
50 
51import net.sf.jomic.common.Settings;
52 
53import org.apache.commons.logging.Log;
54import org.apache.commons.logging.LogFactory;
55 
56import com.jhlabs.image.GaussianFilter;
57import com.jhlabs.image.SmartBlurFilter;
58 
59/**
60 *  Utility methods for working with images.
61 *
62 * @author    Thomas Aglassinger
63 */
64public final class ImageTools
65{
66    public static final int DISTINCT_ROTATIONS_COUNT = 4;
67    public static final String GAUSSIAN_BLUR = "GaussianBlur";
68    public static final int ROTATE_CLOCKWISE = 1;
69    public static final int ROTATE_COUNTERCLOCKWISE = -1;
70    public static final int ROTATE_NONE = 0;
71    public static final int ROTATE_UPSIDE_DOWN = 2;
72    public static final String SCALE_ACTUAL = "actual";
73    public static final String SCALE_FIT = "fit";
74    public static final String SCALE_HEIGHT = "fitHeight";
75    public static final String SCALE_WIDTH = "fitWidth";
76    public static final String THRESHOLD_BLUR = "ThresholdBlur";
77 
78    /**
79     *  Index of brightness in HSB arrays.
80     */
81    private static final int BRIGHTNESS_INDEX = 2;
82    private static final int BROKEN_IMAGE_STROKE_INSET_DIVISOR = 6;
83    private static final int BUSY_IMAGE_STROKE_INSET_DIVISOR = 3;
84    private static final int ROTATION_ANGLE_CLOCKWISE = 90;
85    private static final int ROTATION_ANGLE_COUNTER_CLOCKWISE = -90;
86    private static final int ROTATION_ANGLE_UPSIDE_DOWN = 180;
87    private static final int STROKE_WIDTH_DIVISOR = 10;
88 
89    private static ImageTools instance;
90    private String[] compressedImageFormats;
91 
92    private FileTools fileTools;
93    private Map imageProviderMap;
94    private List imageSuffixList;
95    private Map imageSuffixMap;
96    private LocaleTools localeTools;
97    private Log logger;
98    private String[] possibleBlurModes;
99    private RenderingHints renderHints;
100    private StringTools stringTools;
101 
102    private ImageTools() {
103        logger = LogFactory.getLog(ImageTools.class);
104        fileTools = FileTools.instance();
105        localeTools = LocaleTools.instance();
106        stringTools = StringTools.instance();
107        imageSuffixMap = new TreeMap();
108        imageProviderMap = new TreeMap();
109 
110        compressedImageFormats = new String[]{"gif", "jpeg", "png"};
111        Arrays.sort(compressedImageFormats);
112 
113        possibleBlurModes = new String[]{
114                ImageTools.GAUSSIAN_BLUR, ImageTools.THRESHOLD_BLUR};
115        Arrays.sort(possibleBlurModes);
116 
117        // HACK: Force scanning for plug ins. This should not be necessary, but
118        // otherwise Java Web Start on Mac OS X doesn't seem to find them.
119        ImageIO.scanForPlugins();
120 
121        String[] formats = ImageIO.getReaderFormatNames();
122 
123        for (int i = 0; i < formats.length; i += 1) {
124            String format = formats[i].toLowerCase();
125            Iterator rider = ImageIO.getImageReadersByFormatName(format);
126 
127            assert rider.hasNext() : "no reader for format \"" + format;
128            ImageReader reader = (ImageReader) rider.next();
129            ImageReaderSpi provider = reader.getOriginatingProvider();
130 
131            imageSuffixMap.put(format, format);
132            imageProviderMap.put(format, provider);
133        }
134        renderHints = new RenderingHints(null);
135        renderHints.add(new RenderingHints(
136                RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY));
137        renderHints.add(new RenderingHints(
138                RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
139        renderHints.add(new RenderingHints(
140                RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY));
141        renderHints.add(new RenderingHints(
142                RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE));
143        renderHints.add(new RenderingHints(
144                RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR));
145        renderHints.add(new RenderingHints(
146                RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
147        // HACK: work-around for missing suffix in Apple's TIFF reader.
148        possiblyAddImageFormatCopy("tiff", "tif");
149        possiblyAddImageFormatCopy("jpeg 2000", "jp2");
150        imageSuffixList = new LinkedList(imageSuffixMap.values());
151        Collections.sort(imageSuffixList);
152 
153        if (logger.isInfoEnabled()) {
154            String suffixes = "";
155            Iterator suffixRider = imageSuffixList.iterator();
156 
157            while (suffixRider.hasNext()) {
158                String suffix = (String) suffixRider.next();
159 
160                if (suffixes.length() > 0) {
161                    suffixes += ", ";
162                }
163                suffixes += suffix;
164            }
165            logger.info("supported image file suffixes: " + imageSuffixList);
166            logger.info("supported image providers:");
167 
168            Iterator rider = imageProviderMap.entrySet().iterator();
169 
170            while (rider.hasNext()) {
171                Map.Entry entry = (Map.Entry) rider.next();
172                ImageReaderSpi provider = (ImageReaderSpi) entry.getValue();
173 
174                logger.info("  " + entry.getKey() + ": " + provider.getVendorName() + ", v" + provider.getVersion());
175            }
176 
177            for (int i = 0; i < formats.length; i += 1) {
178                String format = formats[i].toLowerCase();
179 
180                rider = ImageIO.getImageReadersByFormatName(format);
181 
182                ImageReader reader = (ImageReader) rider.next();
183 
184                while (rider.hasNext()) {
185                    reader = (ImageReader) rider.next();
186 
187                    ImageReaderSpi provider = reader.getOriginatingProvider();
188 
189                    logger.info("  " + format + ": " + provider.getVendorName() + ", v" + provider.getVersion());
190                }
191            }
192        }
193 
194        assert imageSuffixList.size() > 0;
195        assert imageProviderMap.size() > 0;
196    }
197 
198    /**
199     *  Convert <code>image</code> to <code>BufferedImage</code>. This is slow.
200     */
201    public BufferedImage getAsBufferedImage(RenderedImage image) {
202        return getAsQuickBufferedImage(image);
203    }
204 
205    /**
206     *  Convert <code>image</code> to <code>ImageIcon</code>. This is slow.
207     */
208    public ImageIcon getAsImageIcon(RenderedImage image) {
209        assert image != null;
210        BufferedImage buffered = getAsBufferedImage(image);
211        ImageIcon result = new ImageIcon(buffered);
212 
213        return result;
214    }
215 
216    /**
217     *  Get image in a form that can be processed quickly by most Java rendering operations.
218     */
219    public BufferedImage getAsQuickBufferedImage(RenderedImage image) {
220        // TODO: Consolidate this with getAsBufferedImage().
221        BufferedImage result;
222        int targetType = BufferedImage.TYPE_INT_RGB;
223 
224        if ((image instanceof BufferedImage) && ((BufferedImage) image).getType() == targetType) {
225            result = (BufferedImage) image;
226        } else {
227            result = new BufferedImage(image.getWidth(), image.getHeight(), targetType);
228 
229            Graphics2D g2d = result.createGraphics();
230 
231            try {
232                AffineTransform identityTransformation = new AffineTransform();
233 
234                g2d.drawRenderedImage(image, identityTransformation);
235            } finally {
236                g2d.dispose();
237            }
238        }
239        return result;
240    }
241 
242    public RenderedImage getBluredImage(RenderedImage image, String blurMode) {
243        assert isValidBlurMode(blurMode) : "blurMode=" + blurMode;
244        BufferedImage result;
245        BufferedImage bufferedSourceImage = getAsBufferedImage(image);
246        Settings settings = Settings.instance();
247        int radius = settings.getBlurRadius();
248        BufferedImageOp blur;
249 
250        if (blurMode.equals(THRESHOLD_BLUR)) {
251            int threshold = settings.getBlurThreshold();
252 
253            blur = new SmartBlurFilter(radius, threshold);
254        } else {
255            if (!blurMode.equals(GAUSSIAN_BLUR)) {
256                logger.warn("unknown blur mode \"" + blurMode + "\", using \"" + GAUSSIAN_BLUR + "\"");
257            }
258            blur = new GaussianFilter(radius);
259        }
260 
261        result = blur.filter(bufferedSourceImage, null);
262        return result;
263    }
264 
265    /**
266     *  Get relative Rotation needed to go from absolute rotation <code>oldRotation</code> to <code>newRotation</code>
267     *  .
268     */
269    public int getDeltaRotation(int oldRotation, int newRotation) {
270        assertIsValidRotation(oldRotation);
271        assertIsValidRotation(newRotation);
272 
273        int result = getFixedRotation(-(oldRotation - newRotation));
274 
275        assertIsValidRotation(result);
276        return result;
277    }
278 
279    /**
280     *  Get the image dimension of <code>imageStream</code> or <code>null</code> if no reader can be
281     *  found.
282     */
283    public Dimension getImageDimension(ImageInputStream imageStream)
284        throws IOException {
285 
286        Dimension result;
287 
288        try {
289            ImageReader reader = getImageReader(imageStream);
290 
291            try {
292                reader.setInput(imageStream);
293 
294                int height = reader.getHeight(0);
295                int width = reader.getWidth(0);
296 
297                result = new Dimension(width, height);
298                if (logger.isDebugEnabled()) {
299                    logger.debug("image dimension: " + result.width + "x" + result.height);
300                }
301            } finally {
302                reader.dispose();
303            }
304        } catch (NoSuchElementException error) {
305            result = null;
306        }
307        return result;
308    }
309 
310    public Dimension getImageDimension(File imageFile)
311        throws IOException {
312        assert imageFile != null;
313 
314        Dimension result;
315        ImageInputStream imageStream = createImageInputStream(imageFile);
316 
317        try {
318            result = getImageDimension(imageStream);
319        } finally {
320            imageStream.close();
321        }
322        return result;
323    }
324 
325    /**
326     *  Get the format of <code>imageStream</code> or <code>null</code> if no image stream or reader
327     *  is available.
328     *
329     * @see    ImageIO#getImageReaders(java.lang.Object)
330     * @see    ImageIO#createImageInputStream(Object)
331     * @see    ImageReader#getFormatName()
332     */
333    public String getImageFormat(ImageInputStream imageStream)
334        throws IOException {
335        String result = null;
336 
337        if (imageStream != null) {
338            Iterator readerRider = ImageIO.getImageReaders(imageStream);
339 
340            while ((result == null) && readerRider.hasNext()) {
341                ImageReader reader = (ImageReader) readerRider.next();
342 
343                result = reader.getFormatName();
344                assert result != null;
345                result = result.toLowerCase();
346                if (logger.isDebugEnabled()) {
347                    logger.debug("image format=" + result);
348                }
349            }
350        }
351        return result;
352    }
353 
354    /**
355     *  Get the format of <code>imageFile</code> or <code>null</code> if no image reader is
356     *  available for it.
357     *
358     * @see    ImageIO#getImageReaders(java.lang.Object)
359     * @see    ImageReader#getFormatName()
360     */
361    public String getImageFormat(File imageFile)
362        throws IOException {
363        String result = null;
364        ImageInputStream imageStream = ImageIO.createImageInputStream(imageFile);
365 
366        if (imageStream != null) {
367            try {
368                result = getImageFormat(imageStream);
369            } finally {
370                imageStream.close();
371            }
372        }
373        return result;
374    }
375 
376    /**
377     *  Get a map with the keys being an image file suffix, and the value being the name of the
378     *  provider handling it.
379     */
380    public Map getImageProviderMap() {
381        return imageProviderMap;
382    }
383 
384    /**
385     *  Get an reader for in the image in <code>imageStream</code>.
386     */
387    public ImageReader getImageReader(ImageInputStream imageStream) {
388        return (ImageReader) ImageIO.getImageReaders(imageStream).next();
389    }
390 
391    public int getLeftRotation(int rotation) {
392        assertIsValidRotation(rotation);
393 
394        int result = getFixedRotation(rotation - 1);
395 
396        return result;
397    }
398 
399    /**
400     *  Like JAI's <code>PlanarImage.getNumBands()</code>, but also works on <code>RenderedImage</code>
401     *  .
402     */
403    public int getNumBands(RenderedImage image) {
404        assert image != null;
405        return image.getColorModel().getColorSpace().getNumComponents();
406    }
407 
408    /**
409     * @see    #getBluredImage(RenderedImage, String)
410     */
411    public String[] getPossibleBlurModes() {
412        return possibleBlurModes;
413    }
414 
415    /**
416     *  RenderingHints used by all render operations within ImageTools.
417     */
418    public RenderingHints getRenderingHints() {
419        return renderHints;
420    }
421 
422    public int getRightRotation(int rotation) {
423        assertIsValidRotation(rotation);
424 
425        int result = getFixedRotation(rotation + 1);
426 
427        return result;
428    }
429 
430    public RenderedImage getRotatedImage(RenderedImage image, double angle) {
431        double sinusOfAngle = Math.abs(Math.sin(angle));
432        double cosinusOfAngle = Math.abs(Math.cos(angle));
433        int imageWidth = image.getWidth();
434        int imageHeight = image.getHeight();
435        int rotatedImageWidth = (int) Math.floor(imageWidth * cosinusOfAngle + imageHeight * sinusOfAngle);
436        int rotatedImageHeight = (int) Math.floor(imageHeight * cosinusOfAngle + imageWidth * sinusOfAngle);
437        BufferedImage result = createBufferedImage(rotatedImageWidth, rotatedImageHeight);
438        Graphics2D g2d = result.createGraphics();
439 
440        try {
441            g2d.translate((rotatedImageWidth - imageWidth) / 2, (rotatedImageHeight - imageHeight) / 2);
442            g2d.rotate(angle, imageWidth / 2, imageHeight / 2);
443            g2d.drawRenderedImage(image, null);
444        } finally {
445            g2d.dispose();
446        }
447        return result;
448    }
449 
450    /**
451     *  Rotate image in steps of 90 degrees.
452     *
453     * @param  rotation  -1=counter clockwise, 0=no rotation, 1=clockwise, 2=upside down
454     */
455    public RenderedImage getRotatedImage(RenderedImage image, int rotation) {
456        assertIsValidRotation(rotation);
457 
458        double radians = 0;
459 
460        if (rotation == ROTATE_CLOCKWISE) {
461            radians = Math.toRadians(ROTATION_ANGLE_CLOCKWISE);
462        } else if (rotation == ROTATE_NONE) {
463            radians = 0;
464        } else if (rotation == ROTATE_UPSIDE_DOWN) {
465            radians = Math.toRadians(ROTATION_ANGLE_UPSIDE_DOWN);
466        } else {
467            assert rotation == ROTATE_COUNTERCLOCKWISE;
468            radians = Math.toRadians(ROTATION_ANGLE_COUNTER_CLOCKWISE);
469        }
470 
471        return getRotatedImage(image, radians);
472    }
473 
474    /**
475     *  Compute scale to squeeze into an area of size <code>areaWidth</code>x<code>areaHeight</code>
476     *  an image of size <code>sourceWidth</code>x<code>sourceHeight</code>.
477     */
478    public double getScaleToSqueeze(
479            int areaWidth, int areaHeight, int sourceWidth, int sourceHeight, String scaleMode) {
480        double result;
481 
482        if (scaleMode.equals(SCALE_FIT)) {
483            double sourceRatio = ((double) sourceWidth) / sourceHeight;
484            double areaRatio = ((double) areaWidth) / areaHeight;
485 
486            if (logger.isDebugEnabled()) {
487                logger.debug("area: " + areaWidth + " / " + areaHeight + " = " + areaRatio);
488                logger.debug("source: " + sourceWidth + " / " + sourceHeight + " = " + sourceRatio);
489            }
490 
491            if (sourceRatio < areaRatio) {
492                result = ((double) areaHeight) / sourceHeight;
493            } else {
494                result = ((double) areaWidth) / sourceWidth;
495            }
496        } else if (scaleMode.equals(SCALE_HEIGHT)) {
497            result = ((double) areaHeight) / sourceHeight;
498        } else if (scaleMode.equals(SCALE_WIDTH)) {
499            result = ((double) areaWidth) / sourceWidth;
500        } else {
501            assert scaleMode.equals(SCALE_ACTUAL) : "scaleMode=" + scaleMode;
502            result = 1.0;
503        }
504 
505        if (logger.isDebugEnabled()) {
506            logger.debug("-> scale = " + result);
507        }
508        return result;
509    }
510 
511    /**
512     *  Get a rescaled version of <code>source</code> that depending on <code>scaleMode</code>
513     *  possibly fits <code>width</code> and/or <code>height</code> while keeping proportions.
514     *
515     * @param  scaleMode  one of: SCALE_ACTUAL, SCALE_FIT, SCALE_WIDTH, SCALE_HEIGHT
516     */
517    public RenderedImage getSqueezed(
518            final RenderedImage source,
519            final int areaWidth,
520            final int areaHeight,
521            final String scaleMode) {
522        assert source != null;
523        assert areaWidth > 0;
524        assert areaHeight > 0;
525        assertIsValidScaleMode(scaleMode);
526 
527        RenderedImage result;
528        int sourceWidth = source.getWidth();
529        int sourceHeight = source.getHeight();
530 
531        if (scaleMode.equals(SCALE_ACTUAL)) {
532            result = source;
533        } else {
534            double scale = getScaleToSqueeze(areaWidth, areaHeight, sourceWidth, sourceHeight, scaleMode);
535 
536            if (scale == 1.0) {
537                result = source;
538            } else {
539                // create the scaled image
540                int targetWidth = (int) Math.round(scale * sourceWidth);
541                int targetHeight = (int) Math.round(scale * sourceHeight);
542                Graphics2D g2d;
543 
544                result = createBufferedImage(targetWidth, targetHeight);
545                g2d = ((BufferedImage) result).createGraphics();
546                try {
547                    g2d.setRenderingHints(renderHints);
548                    g2d.drawRenderedImage(source, AffineTransform.getScaleInstance(scale, scale));
549                } finally {
550                    g2d.dispose();
551                }
552            }
553        }
554 
555        return result;
556    }
557 
558    /**
559     *  Get the size of the actual area used by a sourceWidth x sourceHeight image squeezed in an
560     *  areaWidth x areaHeight area.
561     */
562    public Dimension getSqueezedDimension(
563            int areaWidth, int areaHeight, int sourceWidth, int sourceHeight, String scaleMode) {
564        double scale = getScaleToSqueeze(areaWidth, areaHeight, sourceWidth, sourceHeight, scaleMode);
565        int squeezedWidth = Math.min(areaWidth, (int) Math.round(scale * sourceWidth));
566        int squeezedHeight = Math.min(areaHeight, (int) Math.round(scale * sourceHeight));
567        Dimension result = new Dimension(squeezedWidth, squeezedHeight);
568 
569        return result;
570    }
571 
572    /**
573     *  Yield <code>true</code> if <code>imageFormat</code> indicates a compressed reformat. Images
574     *  in such formats will not shrink significantly by compressing them using for example ZIP.
575     *
576     * @see    #getImageFormat(ImageInputStream)
577     */
578    public boolean isCompressedImageFormat(String imageFormat) {
579        boolean result = Arrays.binarySearch(compressedImageFormats, imageFormat) >= 0;
580 
581        return result;
582    }
583 
584    /**
585     *  Yield <code>true</code> if <code>filePathToCheck</code> indicates an image file. The main
586     *  indicator is the suffix, additionally file names starting with "." are not considered images
587     *  even if the suffix would match. The latter ensures that files storing the resource fork in
588     *  ZIP archives created by Mac OS X's "compress" are ignored.
589     */
590    public boolean isImageFile(String filePathToCheck) {
591        assert filePathToCheck != null;
592        boolean result;
593 
594        if (isImageSuffix(fileTools.getSuffix(filePathToCheck))) {
595            File imageFile = new File(filePathToCheck);
596            String imageName = imageFile.getName();
597 
598            result = !imageName.startsWith(".");
599        } else {
600            result = false;
601        }
602        return result;
603    }
604 
605    public boolean isImageFile(File file) {
606        assert file != null;
607        return isImageSuffix(fileTools.getSuffix(file));
608    }
609 
610    public boolean isImageSuffix(String suffix) {
611        assert suffix != null;
612        return Collections.binarySearch(imageSuffixList, suffix.toLowerCase()) >= 0;
613    }
614 
615    public boolean isLandscape(int width, int height) {
616        return (width > height);
617    }
618 
619    public boolean isLandscape(Dimension size) {
620        return isLandscape(size.width, size.height);
621    }
622 
623    public boolean isLandscape(RenderedImage image) {
624        return isLandscape(image.getWidth(), image.getHeight());
625    }
626 
627    public boolean isValidBlurMode(String some) {
628        return stringTools.equalsAnyOf(possibleBlurModes, some);
629    }
630 
631    public boolean isValidRotation(int rotation) {
632        return (rotation == ROTATE_NONE)
633                || (rotation == ROTATE_CLOCKWISE)
634                || (rotation == ROTATE_UPSIDE_DOWN)
635                || (rotation == ROTATE_COUNTERCLOCKWISE);
636    }
637 
638    public boolean isValidScaleMode(String mode) {
639        return (mode != null) && (mode.equals(ImageTools.SCALE_ACTUAL)
640                || mode.equals(ImageTools.SCALE_FIT)
641                || mode.equals(ImageTools.SCALE_HEIGHT)
642                || mode.equals(ImageTools.SCALE_WIDTH));
643    }
644 
645    /**
646     *  Get adjusted rotation so that it fits in valid range.
647     */
648    int getFixedRotation(int possiblyInvalidRotation) {
649        int result = ((possiblyInvalidRotation + 1) % DISTINCT_ROTATIONS_COUNT) - 1;
650 
651        if (result < -1) {
652            result += DISTINCT_ROTATIONS_COUNT;
653        }
654        assertIsValidRotation(result);
655        return result;
656    }
657 
658 
659    /**
660     *  Get accessor to unique instance.
661     */
662    public static synchronized ImageTools instance() {
663        if (instance == null) {
664            instance = new ImageTools();
665        }
666        return instance;
667    }
668 
669    public void assertIsValidRotation(int rotation) {
670        assert (rotation >= -1) && (rotation <= 2) : "rotation = " + rotation;
671    }
672 
673    public void assertIsValidScaleMode(String scaleMode) {
674        assert isValidScaleMode(scaleMode) : "scaleMode = " + scaleMode;
675    }
676 
677    /**
678     *  Create image to represent a broken item.
679     */
680    public RenderedImage createBrokenImage(int width, int height, Color background, Color foreground) {
681        assert width > 0;
682        assert height > 0;
683        assert background != null;
684        assert foreground != null;
685 
686        BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
687        Graphics2D g = result.createGraphics();
688 
689        try {
690            float strokeWidth = Math.max(1f, ((float) Math.min(width, height)) / STROKE_WIDTH_DIVISOR);
691            int insetX = width / BROKEN_IMAGE_STROKE_INSET_DIVISOR;
692            int insetY = height / BROKEN_IMAGE_STROKE_INSET_DIVISOR;
693 
694            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
695            g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
696            g.setColor(background);
697            g.fillRect(0, 0, width, height);
698            g.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
699            g.setColor(foreground);
700            g.drawLine(insetX, insetY, width - insetX, height - insetY);
701            g.drawLine(width - insetX, insetY, insetX, height - insetY);
702        } finally {
703            g.dispose();
704        }
705        return result;
706    }
707 
708    /**
709     *  Create image to represent a busy image that is in process of being rendered.
710     */
711    public RenderedImage createBusyImage(int width, int height, Color background, Color foreground) {
712        assert width > 0;
713        assert height > 0;
714        assert background != null;
715        assert foreground != null;
716 
717        BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
718        Graphics2D g = result.createGraphics();
719 
720        try {
721            float strokeWidth = Math.max(1f, ((float) Math.min(width, height)) / STROKE_WIDTH_DIVISOR);
722            float baseDiameter = Math.min(width, height);
723            int inset = Math.min(width / BUSY_IMAGE_STROKE_INSET_DIVISOR,
724                    height / BUSY_IMAGE_STROKE_INSET_DIVISOR);
725            float radius = baseDiameter - 2 * inset;
726            Ellipse2D.Double ellipse = new Ellipse2D.Double(
727                    (width - radius) / 2, (height - radius) / 2, radius, radius);
728 
729            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
730            g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
731            g.setColor(background);
732            g.fillRect(0, 0, width, height);
733            g.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
734            g.setColor(foreground);
735            g.draw(ellipse);
736        } finally {
737            g.dispose();
738        }
739        return result;
740    }
741 
742    /**
743     *  Create a rectangle of a certain color.
744     */
745    public BufferedImage createColorBox(int width, int height, Color color) {
746        assert width > 0;
747        assert height > 0;
748        assert color != null;
749        BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
750        Graphics2D g = result.createGraphics();
751 
752        try {
753            g.setColor(color);
754            g.fillRect(0, 0, width, height);
755            g.setColor(Color.BLACK);
756            g.drawRect(0, 0, width - 1, height - 1);
757        } finally {
758            g.dispose();
759        }
760        return result;
761    }
762 
763    /**
764     *  Same as ImageIO.createImageInputStream, but throws a <code>FileNotFoundException</code> if
765     *  <code>imageFile</code> cannot be found (instead of returning <code>null</code>). Why the
766     *  original does not work that way already is beyond me.<p>
767     *
768     *  Note that a non-image (such as a text file) still returns proper stream.
769     */
770    public ImageInputStream createImageInputStream(File imageFile)
771        throws IOException {
772        assert imageFile != null;
773        ImageInputStream result = ImageIO.createImageInputStream(imageFile);
774 
775        if (result == null) {
776            String message = localeTools.getMessage("errors.cannotFindImageFile", imageFile);
777 
778            throw new FileNotFoundException(message);
779        }
780        return result;
781    }
782 
783    /**
784     *  Compute fill values for <code>color</code> when it should be used by the "border" operator
785     *  on an image with a ColorModel of <code>colorModel</code>.
786     */
787    public double[] fillColorValues(Color color, ColorModel colorModel) {
788        assert color != null;
789        assert colorModel != null;
790 
791        double[] result;
792        ColorSpace colorSpace = colorModel.getColorSpace();
793        boolean isGray = colorSpace.getType() == ColorSpace.TYPE_GRAY;
794        float[] fillValuesFloat;
795 
796        if (isGray) {
797            // In case of grayscale images, use brightness instead of color
798            float[] hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
799            float brightness = hsb[BRIGHTNESS_INDEX];
800 
801            assert colorSpace.getNumComponents() == 1;
802            float minValue = colorSpace.getMinValue(0);
803            float maxValue = colorSpace.getMaxValue(0);
804            float range = maxValue - minValue;
805 
806            fillValuesFloat = new float[]{brightness * range + minValue};
807        } else {
808            // Otherwise, just use the color components
809            fillValuesFloat = color.getColorComponents(colorSpace, null);
810        }
811 
812        // Convert float[] to double[]
813        result = new double[fillValuesFloat.length];
814        for (int i = 0; i < fillValuesFloat.length; i += 1) {
815            result[i] = ((1 << colorModel.getComponentSize(i)) - 1) * fillValuesFloat[i];
816        }
817        if (logger.isInfoEnabled()) {
818            String colorText = stringTools.colorString(color.getRGB());
819 
820            logger.info("fillValues for " + colorText + ": " + stringTools.arrayToString(result));
821        }
822        return result;
823    }
824 
825    /**
826     *  Read the specified <code>imageFile</code>.
827     */
828    public RenderedImage readImage(File imageFile)
829        throws IOException {
830        assert imageFile != null;
831        RenderedImage result;
832        Exception errorCause = null;
833        ImageInputStream in = createImageInputStream(imageFile);
834 
835        try {
836            ImageReader reader = getImageReader(in);
837 
838            try {
839                reader.setInput(in);
840                try {
841                    ImageReadParam readParameters = reader.getDefaultReadParam();
842 
843                    readParameters.setDestinationType(
844                            ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB));
845                    result = reader.read(0);
846                } finally {
847                    reader.dispose();
848                }
849            } finally {
850                in.close();
851            }
852        } catch (Exception error) {
853            result = null;
854            errorCause = error;
855        }
856        if (result == null) {
857            String message = localeTools.getMessage("errors.cannotReadImageFile", imageFile);
858 
859            throw new IIOException(message, errorCause);
860        }
861        result = getAsQuickBufferedImage(result);
862        return result;
863    }
864 
865    private BufferedImage createBufferedImage(int width, int height) {
866        assert width >= 0;
867        assert height >= 0;
868        return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
869    }
870 
871    /**
872     *  If there is a provider for suffix <code>original</code> but none for suffix <code>copy</code>
873     *  , add the same provider for it.
874     */
875    private void possiblyAddImageFormatCopy(String original, String copy) {
876        assert original != null;
877        assert copy != null;
878        assert original.equals(original.toLowerCase());
879        assert copy.equals(copy.toLowerCase());
880        assert !original.equals(copy);
881 
882        Object originalProvider = imageProviderMap.get(original);
883 
884        if (originalProvider != null) {
885            Object copyProvider = imageProviderMap.get(copy);
886 
887            if (copyProvider == null) {
888                logger.warn("adding suffix " + stringTools.sourced(copy)
889                        + " to broken provider for suffix " + stringTools.sourced(original));
890                imageProviderMap.put(copy, originalProvider);
891                imageSuffixMap.put(copy, copy);
892            }
893        }
894    }
895}

[all classes][net.sf.jomic.tools]
EMMA 2.0.4217 (C) Vladimir Roubtsov