Merge branch 'subtree'
[nikiroo-utils.git] / src / be / nikiroo / utils / ui / ImageUtilsAwt.java
index e1fcac7bd053e8b67d87a8ff5e95edb972bfffd0..c273e0d45879628543dcab75f2c7c88f133224d9 100644 (file)
@@ -1,17 +1,20 @@
 package be.nikiroo.utils.ui;
 
+import java.awt.Dimension;
+import java.awt.Graphics2D;
 import java.awt.geom.AffineTransform;
 import java.awt.image.AffineTransformOp;
 import java.awt.image.BufferedImage;
-import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 
 import javax.imageio.ImageIO;
 
+import be.nikiroo.utils.IOUtils;
 import be.nikiroo.utils.Image;
 import be.nikiroo.utils.ImageUtils;
+import be.nikiroo.utils.StringUtils;
 
 /**
  * This class offer some utilities based around images and uses java.awt.
@@ -19,11 +22,34 @@ import be.nikiroo.utils.ImageUtils;
  * @author niki
  */
 public class ImageUtilsAwt extends ImageUtils {
+       /**
+        * A rotation to perform on an image.
+        * 
+        * @author niki
+        */
+       public enum Rotation {
+               /** No rotation */
+               NONE,
+               /** Rotate the image to the right */
+               RIGHT,
+               /** Rotate the image to the left */
+               LEFT,
+               /** Rotate the image by 180° */
+               UTURN
+       }
+
+       @Override
+       protected boolean check() {
+               // Will not work if ImageIO is not available
+               ImageIO.getCacheDirectory();
+               return true;
+       }
+
        @Override
        public void saveAsImage(Image img, File target, String format)
                        throws IOException {
                try {
-                       BufferedImage image = ImageUtilsAwt.fromImage(img);
+                       BufferedImage image = fromImage(img);
 
                        boolean ok = false;
                        try {
@@ -34,9 +60,16 @@ public class ImageUtilsAwt extends ImageUtils {
                        }
 
                        // Some formats are not reliable
-                       // Second change: PNG
+                       // Second chance: PNG
                        if (!ok && !format.equals("png")) {
-                               ok = ImageIO.write(image, "png", target);
+                               try {
+                                       ok = ImageIO.write(image, "png", target);
+                               } catch (IllegalArgumentException e) {
+                                       throw e;
+                               } catch (Exception e) {
+                                       throw new IOException("Undocumented exception occured, "
+                                                       + "converting to IOException", e);
+                               }
                        }
 
                        if (!ok) {
@@ -61,82 +94,241 @@ public class ImageUtilsAwt extends ImageUtils {
         * @throws IOException
         *             in case of IO error
         */
-       static public BufferedImage fromImage(Image img) throws IOException {
-               InputStream in = new ByteArrayInputStream(img.getData());
+       public static BufferedImage fromImage(Image img) throws IOException {
+               return fromImage(img, Rotation.NONE);
+       }
 
-               int orientation;
+       /**
+        * Convert the given {@link Image} into a {@link BufferedImage} object,
+        * respecting the EXIF transformations if any.
+        * 
+        * @param img
+        *            the {@link Image}
+        * @param rotation
+        *            the rotation to apply, if any (can be null, same as
+        *            {@link Rotation#NONE})
+        * 
+        * @return the {@link Image} object
+        * 
+        * @throws IOException
+        *             in case of IO error
+        */
+       public static BufferedImage fromImage(Image img, Rotation rotation)
+                       throws IOException {
+               InputStream in = img.newInputStream();
+               BufferedImage image;
                try {
-                       orientation = getExifTransorm(in);
-               } catch (Exception e) {
-                       // no EXIF transform, ok
-                       orientation = -1;
-               }
+                       int orientation;
+                       try {
+                               orientation = getExifTransorm(in);
+                       } catch (Exception e) {
+                               // no EXIF transform, ok
+                               orientation = -1;
+                       }
 
-               in.reset();
-               BufferedImage image = ImageIO.read(in);
+                       in.reset();
 
-               if (image == null) {
-                       throw new IOException("Failed to convert input to image");
-               }
+                       try {
+                               image = ImageIO.read(in);
+                       } catch (IllegalArgumentException e) {
+                               throw e;
+                       } catch (Exception e) {
+                               throw new IOException("Undocumented exception occured, "
+                                               + "converting to IOException", e);
+                       }
 
-               // Note: this code has been found on Internet;
-               // thank you anonymous coder.
-               int width = image.getWidth();
-               int height = image.getHeight();
-               AffineTransform affineTransform = new AffineTransform();
-
-               switch (orientation) {
-               case 1:
-                       affineTransform = null;
-                       break;
-               case 2: // Flip X
-                       affineTransform.scale(-1.0, 1.0);
-                       affineTransform.translate(-width, 0);
-                       break;
-               case 3: // PI rotation
-                       affineTransform.translate(width, height);
-                       affineTransform.rotate(Math.PI);
-                       break;
-               case 4: // Flip Y
-                       affineTransform.scale(1.0, -1.0);
-                       affineTransform.translate(0, -height);
-                       break;
-               case 5: // - PI/2 and Flip X
-                       affineTransform.rotate(-Math.PI / 2);
-                       affineTransform.scale(-1.0, 1.0);
-                       break;
-               case 6: // -PI/2 and -width
-                       affineTransform.translate(height, 0);
-                       affineTransform.rotate(Math.PI / 2);
-                       break;
-               case 7: // PI/2 and Flip
-                       affineTransform.scale(-1.0, 1.0);
-                       affineTransform.translate(-height, 0);
-                       affineTransform.translate(0, width);
-                       affineTransform.rotate(3 * Math.PI / 2);
-                       break;
-               case 8: // PI / 2
-                       affineTransform.translate(0, width);
-                       affineTransform.rotate(3 * Math.PI / 2);
-                       break;
-               default:
-                       affineTransform = null;
-                       break;
-               }
+                       if (image == null) {
+                               String extra = "";
+                               if (img.getSize() <= 2048) {
+                                       try {
+                                               byte[] data = null;
+                                               InputStream inData = img.newInputStream();
+                                               try {
+                                                       data = IOUtils.toByteArray(inData);
+                                               } finally {
+                                                       inData.close();
+                                               }
+                                               extra = ", content: " + new String(data, "UTF-8");
+                                       } catch (Exception e) {
+                                               extra = ", content unavailable";
+                                       }
+                               }
+                               String ssize = StringUtils.formatNumber(img.getSize());
+                               throw new IOException(
+                                               "Failed to convert input to image, size was: " + ssize
+                                                               + extra);
+                       }
+
+                       // Note: this code has been found on Internet;
+                       // thank you anonymous coder.
+                       int width = image.getWidth();
+                       int height = image.getHeight();
+                       AffineTransform affineTransform = new AffineTransform();
 
-               if (affineTransform != null) {
-                       AffineTransformOp affineTransformOp = new AffineTransformOp(
-                                       affineTransform, AffineTransformOp.TYPE_BILINEAR);
+                       switch (orientation) {
+                       case 1:
+                               affineTransform = null;
+                               break;
+                       case 2: // Flip X
+                               affineTransform.scale(-1.0, 1.0);
+                               affineTransform.translate(-width, 0);
+                               break;
+                       case 3: // PI rotation
+                               affineTransform.translate(width, height);
+                               affineTransform.rotate(Math.PI);
+                               break;
+                       case 4: // Flip Y
+                               affineTransform.scale(1.0, -1.0);
+                               affineTransform.translate(0, -height);
+                               break;
+                       case 5: // - PI/2 and Flip X
+                               affineTransform.rotate(-Math.PI / 2);
+                               affineTransform.scale(-1.0, 1.0);
+                               break;
+                       case 6: // -PI/2 and -width
+                               affineTransform.translate(height, 0);
+                               affineTransform.rotate(Math.PI / 2);
+                               break;
+                       case 7: // PI/2 and Flip
+                               affineTransform.scale(-1.0, 1.0);
+                               affineTransform.translate(-height, 0);
+                               affineTransform.translate(0, width);
+                               affineTransform.rotate(3 * Math.PI / 2);
+                               break;
+                       case 8: // PI / 2
+                               affineTransform.translate(0, width);
+                               affineTransform.rotate(3 * Math.PI / 2);
+                               break;
+                       default:
+                               affineTransform = null;
+                               break;
+                       }
+
+                       if (rotation == null)
+                               rotation = Rotation.NONE;
+
+                       switch (rotation) {
+                       case RIGHT:
+                               if (affineTransform == null) {
+                                       affineTransform = new AffineTransform();
+                               }
+                               affineTransform.translate(height, 0);
+                               affineTransform.rotate(Math.PI / 2);
+
+                               int tmp = width;
+                               width = height;
+                               height = tmp;
+
+                               break;
+                       case LEFT:
+                               if (affineTransform == null) {
+                                       affineTransform = new AffineTransform();
+                               }
+                               affineTransform.translate(0, width);
+                               affineTransform.rotate(3 * Math.PI / 2);
+
+                               int temp = width;
+                               width = height;
+                               height = temp;
+
+                               break;
+                       case UTURN:
+                               if (affineTransform == null) {
+                                       affineTransform = new AffineTransform();
+                               }
+                               affineTransform.translate(width, height);
+                               affineTransform.rotate(Math.PI);
+                               break;
+                       default:
+                               break;
+                       }
 
-                       BufferedImage transformedImage = new BufferedImage(width, height,
-                                       image.getType());
-                       transformedImage = affineTransformOp
-                                       .filter(image, transformedImage);
+                       if (affineTransform != null) {
+                               AffineTransformOp affineTransformOp = new AffineTransformOp(
+                                               affineTransform, AffineTransformOp.TYPE_BILINEAR);
 
-                       image = transformedImage;
+                               BufferedImage transformedImage = new BufferedImage(width,
+                                               height, image.getType());
+                               transformedImage = affineTransformOp.filter(image,
+                                               transformedImage);
+
+                               image = transformedImage;
+                       }
+                       //
+               } finally {
+                       in.close();
                }
-               //
 
                return image;
        }
+
+       /**
+        * Scale a dimension.
+        * 
+        * @param imageSize
+        *            the actual image size
+        * @param areaSize
+        *            the base size of the target to get snap sizes for
+        * @param zoom
+        *            the zoom factor (ignored on snap mode)
+        * @param snapMode
+        *            NULL for no snap mode, TRUE to snap to width and FALSE for
+        *            snap to height)
+        * 
+        * @return the scaled (minimum is 1x1)
+        */
+       public static Dimension scaleSize(Dimension imageSize, Dimension areaSize,
+                       double zoom, Boolean snapMode) {
+               Integer[] sz = scaleSize(imageSize.width, imageSize.height,
+                               areaSize.width, areaSize.height, zoom, snapMode);
+               return new Dimension(sz[0], sz[1]);
+       }
+
+       /**
+        * Resize the given image.
+        * 
+        * @param image
+        *            the image to resize
+        * @param areaSize
+        *            the base size of the target dimension for snap sizes
+        * @param zoom
+        *            the zoom factor (ignored on snap mode)
+        * @param snapMode
+        *            NULL for no snap mode, TRUE to snap to width and FALSE for
+        *            snap to height)
+        * 
+        * @return a new, resized image
+        */
+       public static BufferedImage scaleImage(BufferedImage image,
+                       Dimension areaSize, double zoom, Boolean snapMode) {
+               Dimension scaledSize = scaleSize(
+                               new Dimension(image.getWidth(), image.getHeight()), areaSize,
+                               zoom, snapMode);
+
+               return scaleImage(image, scaledSize);
+       }
+
+       /**
+        * Resize the given image.
+        * 
+        * @param image
+        *            the image to resize
+        * @param targetSize
+        *            the target size
+        * 
+        * @return a new, resized image
+        */
+       public static BufferedImage scaleImage(BufferedImage image,
+                       Dimension targetSize) {
+               BufferedImage resizedImage = new BufferedImage(targetSize.width,
+                               targetSize.height, BufferedImage.TYPE_4BYTE_ABGR);
+               Graphics2D g = resizedImage.createGraphics();
+               try {
+                       g.drawImage(image, 0, 0, targetSize.width, targetSize.height, null);
+               } finally {
+                       g.dispose();
+               }
+
+               return resizedImage;
+       }
 }