From: Niki Roo Date: Thu, 16 Feb 2017 18:05:33 +0000 (+0100) Subject: VERSION 0.9.7: better toImage (allows non resetable streams) X-Git-Tag: nikiroo-utils-1.1.0~2 X-Git-Url: https://git.nikiroo.be/?a=commitdiff_plain;h=b607df60a14942d9aa6383144470a237934eecfc;p=nikiroo-utils.git VERSION 0.9.7: better toImage (allows non resetable streams) --- diff --git a/VERSION b/VERSION index 85b7c69..c81aa44 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.6 +0.9.7 diff --git a/changelog b/changelog index 42204f6..e1c2d8f 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,15 @@ +Version 0.9.7 +------------- + +Improve toImage and allow non-resetable InputStreams + ...though they are then automatically saved onto disk then re-opened, + then the file is deleted at the end of the process -- bad perfs + Worse, it does it even if no EXIF metadata are present (because it + cannot know that before reading the Stream, and cannot save a + partially, non-resetable Stream to disk) + +Reoarganize some methods from String to IO + Version 0.9.6 ------------- diff --git a/src/be/nikiroo/utils/Base64.java b/src/be/nikiroo/utils/Base64.java index f123b32..5ee3d2e 100644 --- a/src/be/nikiroo/utils/Base64.java +++ b/src/be/nikiroo/utils/Base64.java @@ -1359,7 +1359,8 @@ class Base64 @Override public Class resolveClass(java.io.ObjectStreamClass streamClass) throws java.io.IOException, ClassNotFoundException { - Class c = Class.forName(streamClass.getName(), false, loader); + @SuppressWarnings("rawtypes") + Class c = Class.forName(streamClass.getName(), false, loader); if( c == null ){ return super.resolveClass(streamClass); } else { diff --git a/src/be/nikiroo/utils/IOUtils.java b/src/be/nikiroo/utils/IOUtils.java index 09b0cb9..92e3128 100644 --- a/src/be/nikiroo/utils/IOUtils.java +++ b/src/be/nikiroo/utils/IOUtils.java @@ -1,5 +1,9 @@ package be.nikiroo.utils; +import java.awt.Image; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -12,6 +16,8 @@ import java.io.OutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; +import javax.imageio.ImageIO; + /** * This class offer some utilities based around Streams. * @@ -212,4 +218,263 @@ public class IOUtils { + target.getAbsolutePath()); } } + + /** + * Convert the given {@link InputStream} (which should allow calls to + * {@link InputStream#reset() for better perfs}) into an {@link Image} + * object, respecting the EXIF transformations if any. + * + * @param in + * the 'resetable' {@link InputStream} + * + * @return the {@link Image} object + * + * @throws IOException + * in case of IO error + */ + static public BufferedImage toImage(InputStream in) throws IOException { + MarkableFileInputStream tmpIn = null; + File tmp = null; + try { + in.reset(); + } catch (IOException e) { + tmp = File.createTempFile("fanfic-tmp-image", ".tmp"); + tmp.deleteOnExit(); + IOUtils.write(in, tmp); + tmpIn = new MarkableFileInputStream(new FileInputStream(tmp)); + } + + int orientation; + try { + orientation = getExifTransorm(in); + } catch (Exception e) { + // no EXIF transform, ok + orientation = -1; + } + + in.reset(); + BufferedImage image = ImageIO.read(in); + + if (image == null) { + if (tmp != null) { + tmp.delete(); + tmpIn.close(); + } + throw new IOException("Failed to convert input to image"); + } + + // 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 (affineTransform != null) { + AffineTransformOp affineTransformOp = new AffineTransformOp( + affineTransform, AffineTransformOp.TYPE_BILINEAR); + + BufferedImage transformedImage = new BufferedImage(width, height, + image.getType()); + transformedImage = affineTransformOp + .filter(image, transformedImage); + + image = transformedImage; + } + // + + if (tmp != null) { + tmp.delete(); + tmpIn.close(); + } + + return image; + } + + /** + * Return the EXIF transformation flag of this image if any. + * + *

+ * Note: this code has been found on internet; thank you anonymous coder. + *

+ * + * @param in + * the data {@link InputStream} + * + * @return the transformation flag if any + * + * @throws IOException + * in case of IO error + */ + static private int getExifTransorm(InputStream in) throws IOException { + int[] exif_data = new int[100]; + int set_flag = 0; + int is_motorola = 0; + + /* Read File head, check for JPEG SOI + Exif APP1 */ + for (int i = 0; i < 4; i++) + exif_data[i] = in.read(); + + if (exif_data[0] != 0xFF || exif_data[1] != 0xD8 + || exif_data[2] != 0xFF || exif_data[3] != 0xE1) + return -2; + + /* Get the marker parameter length count */ + int length = (in.read() << 8 | in.read()); + + /* Length includes itself, so must be at least 2 */ + /* Following Exif data length must be at least 6 */ + if (length < 8) + return -1; + length -= 8; + /* Read Exif head, check for "Exif" */ + for (int i = 0; i < 6; i++) + exif_data[i] = in.read(); + + if (exif_data[0] != 0x45 || exif_data[1] != 0x78 + || exif_data[2] != 0x69 || exif_data[3] != 0x66 + || exif_data[4] != 0 || exif_data[5] != 0) + return -1; + + /* Read Exif body */ + length = length > exif_data.length ? exif_data.length : length; + for (int i = 0; i < length; i++) + exif_data[i] = in.read(); + + if (length < 12) + return -1; /* Length of an IFD entry */ + + /* Discover byte order */ + if (exif_data[0] == 0x49 && exif_data[1] == 0x49) + is_motorola = 0; + else if (exif_data[0] == 0x4D && exif_data[1] == 0x4D) + is_motorola = 1; + else + return -1; + + /* Check Tag Mark */ + if (is_motorola == 1) { + if (exif_data[2] != 0) + return -1; + if (exif_data[3] != 0x2A) + return -1; + } else { + if (exif_data[3] != 0) + return -1; + if (exif_data[2] != 0x2A) + return -1; + } + + /* Get first IFD offset (offset to IFD0) */ + int offset; + if (is_motorola == 1) { + if (exif_data[4] != 0) + return -1; + if (exif_data[5] != 0) + return -1; + offset = exif_data[6]; + offset <<= 8; + offset += exif_data[7]; + } else { + if (exif_data[7] != 0) + return -1; + if (exif_data[6] != 0) + return -1; + offset = exif_data[5]; + offset <<= 8; + offset += exif_data[4]; + } + if (offset > length - 2) + return -1; /* check end of data segment */ + + /* Get the number of directory entries contained in this IFD */ + int number_of_tags; + if (is_motorola == 1) { + number_of_tags = exif_data[offset]; + number_of_tags <<= 8; + number_of_tags += exif_data[offset + 1]; + } else { + number_of_tags = exif_data[offset + 1]; + number_of_tags <<= 8; + number_of_tags += exif_data[offset]; + } + if (number_of_tags == 0) + return -1; + offset += 2; + + /* Search for Orientation Tag in IFD0 */ + for (;;) { + if (offset > length - 12) + return -1; /* check end of data segment */ + /* Get Tag number */ + int tagnum; + if (is_motorola == 1) { + tagnum = exif_data[offset]; + tagnum <<= 8; + tagnum += exif_data[offset + 1]; + } else { + tagnum = exif_data[offset + 1]; + tagnum <<= 8; + tagnum += exif_data[offset]; + } + if (tagnum == 0x0112) + break; /* found Orientation Tag */ + if (--number_of_tags == 0) + return -1; + offset += 12; + } + + /* Get the Orientation value */ + if (is_motorola == 1) { + if (exif_data[offset + 8] != 0) + return -1; + set_flag = exif_data[offset + 9]; + } else { + if (exif_data[offset + 9] != 0) + return -1; + set_flag = exif_data[offset + 8]; + } + if (set_flag > 8) + return -1; + + return set_flag; + } } diff --git a/src/be/nikiroo/utils/StringUtils.java b/src/be/nikiroo/utils/StringUtils.java index ed3c630..cd9a463 100644 --- a/src/be/nikiroo/utils/StringUtils.java +++ b/src/be/nikiroo/utils/StringUtils.java @@ -1,8 +1,6 @@ package be.nikiroo.utils; import java.awt.Image; -import java.awt.geom.AffineTransform; -import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -274,95 +272,7 @@ public class StringUtils { static public BufferedImage toImage(String b64data) throws IOException { ByteArrayInputStream in = new ByteArrayInputStream( Base64.decode(b64data)); - return toImage(in); - } - - /** - * Convert the given {@link InputStream} (which must allow calls to - * {@link InputStream#reset()}) into an {@link Image} object. - * - * @param in - * the 'resetable' {@link InputStream} - * - * @return the {@link Image} object - * - * @throws IOException - * in case of IO error - */ - static public BufferedImage toImage(InputStream in) throws IOException { - int orientation; - try { - orientation = getExifTransorm(in); - } catch (Exception e) { - // no EXIF transform, ok - orientation = -1; - } - - in.reset(); - BufferedImage image = ImageIO.read(in); - - if (image == null) { - throw new IOException("Failed to convert input to image"); - } - - // 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: - 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 (affineTransform != null) { - AffineTransformOp affineTransformOp = new AffineTransformOp( - affineTransform, AffineTransformOp.TYPE_BILINEAR); - - BufferedImage transformedImage = new BufferedImage(height, width, - image.getType()); - transformedImage = affineTransformOp - .filter(image, transformedImage); - - image = transformedImage; - } - // - - return image; + return IOUtils.toImage(in); } /** @@ -393,155 +303,6 @@ public class StringUtils { } } - /** - * Return the EXIF transformation flag of this image if any. - * - *

- * Note: this code has been found on internet; thank you anonymous coder. - *

- * - * @param in - * the data {@link InputStream} - * - * @return the transformation flag if any - * - * @throws IOException - * in case of IO error - */ - static private int getExifTransorm(InputStream in) throws IOException { - int[] exif_data = new int[100]; - int set_flag = 0; - int is_motorola = 0; - - /* Read File head, check for JPEG SOI + Exif APP1 */ - for (int i = 0; i < 4; i++) - exif_data[i] = in.read(); - - if (exif_data[0] != 0xFF || exif_data[1] != 0xD8 - || exif_data[2] != 0xFF || exif_data[3] != 0xE1) - return -2; - - /* Get the marker parameter length count */ - int length = (in.read() << 8 | in.read()); - - /* Length includes itself, so must be at least 2 */ - /* Following Exif data length must be at least 6 */ - if (length < 8) - return -1; - length -= 8; - /* Read Exif head, check for "Exif" */ - for (int i = 0; i < 6; i++) - exif_data[i] = in.read(); - - if (exif_data[0] != 0x45 || exif_data[1] != 0x78 - || exif_data[2] != 0x69 || exif_data[3] != 0x66 - || exif_data[4] != 0 || exif_data[5] != 0) - return -1; - - /* Read Exif body */ - length = length > exif_data.length ? exif_data.length : length; - for (int i = 0; i < length; i++) - exif_data[i] = in.read(); - - if (length < 12) - return -1; /* Length of an IFD entry */ - - /* Discover byte order */ - if (exif_data[0] == 0x49 && exif_data[1] == 0x49) - is_motorola = 0; - else if (exif_data[0] == 0x4D && exif_data[1] == 0x4D) - is_motorola = 1; - else - return -1; - - /* Check Tag Mark */ - if (is_motorola == 1) { - if (exif_data[2] != 0) - return -1; - if (exif_data[3] != 0x2A) - return -1; - } else { - if (exif_data[3] != 0) - return -1; - if (exif_data[2] != 0x2A) - return -1; - } - - /* Get first IFD offset (offset to IFD0) */ - int offset; - if (is_motorola == 1) { - if (exif_data[4] != 0) - return -1; - if (exif_data[5] != 0) - return -1; - offset = exif_data[6]; - offset <<= 8; - offset += exif_data[7]; - } else { - if (exif_data[7] != 0) - return -1; - if (exif_data[6] != 0) - return -1; - offset = exif_data[5]; - offset <<= 8; - offset += exif_data[4]; - } - if (offset > length - 2) - return -1; /* check end of data segment */ - - /* Get the number of directory entries contained in this IFD */ - int number_of_tags; - if (is_motorola == 1) { - number_of_tags = exif_data[offset]; - number_of_tags <<= 8; - number_of_tags += exif_data[offset + 1]; - } else { - number_of_tags = exif_data[offset + 1]; - number_of_tags <<= 8; - number_of_tags += exif_data[offset]; - } - if (number_of_tags == 0) - return -1; - offset += 2; - - /* Search for Orientation Tag in IFD0 */ - for (;;) { - if (offset > length - 12) - return -1; /* check end of data segment */ - /* Get Tag number */ - int tagnum; - if (is_motorola == 1) { - tagnum = exif_data[offset]; - tagnum <<= 8; - tagnum += exif_data[offset + 1]; - } else { - tagnum = exif_data[offset + 1]; - tagnum <<= 8; - tagnum += exif_data[offset]; - } - if (tagnum == 0x0112) - break; /* found Orientation Tag */ - if (--number_of_tags == 0) - return -1; - offset += 12; - } - - /* Get the Orientation value */ - if (is_motorola == 1) { - if (exif_data[offset + 8] != 0) - return -1; - set_flag = exif_data[offset + 9]; - } else { - if (exif_data[offset + 9] != 0) - return -1; - set_flag = exif_data[offset + 8]; - } - if (set_flag > 8) - return -1; - - return set_flag; - } - /** * Remove the HTML content from the given input, and un-html-ize the rest. *