1 package be
.nikiroo
.utils
;
3 import java
.awt
.Dimension
;
5 import java
.awt
.geom
.AffineTransform
;
6 import java
.awt
.image
.AffineTransformOp
;
7 import java
.awt
.image
.BufferedImage
;
8 import java
.io
.ByteArrayInputStream
;
9 import java
.io
.ByteArrayOutputStream
;
11 import java
.io
.FileInputStream
;
12 import java
.io
.IOException
;
13 import java
.io
.InputStream
;
15 import javax
.imageio
.ImageIO
;
17 import be
.nikiroo
.utils
.ImageText
.Mode
;
20 * This class offer some utilities based around images.
24 public class ImageUtils
{
26 * Convert the given {@link Image} object into a Base64 representation of
27 * the same {@link Image} object.
30 * the {@link Image} object to convert
32 * @return the Base64 representation
37 static public String
toBase64(BufferedImage image
) throws IOException
{
38 return toBase64(image
, null);
42 * Convert the given {@link Image} object into a Base64 representation of
43 * the same {@link Image}. object.
46 * the {@link Image} object to convert
48 * the image format to use to serialise it (default is PNG)
50 * @return the Base64 representation
55 static public String
toBase64(BufferedImage image
, String format
)
61 String imageString
= null;
62 ByteArrayOutputStream out
= new ByteArrayOutputStream();
64 ImageIO
.write(image
, format
, out
);
65 byte[] imageBytes
= out
.toByteArray();
67 imageString
= new String(Base64
.encodeBytes(imageBytes
));
75 * Convert the given image into a Base64 representation of the same
79 * the image to convert
81 * @return the Base64 representation
86 static public String
toBase64(InputStream in
) throws IOException
{
87 String fileString
= null;
88 ByteArrayOutputStream out
= new ByteArrayOutputStream();
90 byte[] buf
= new byte[8192];
93 while ((c
= in
.read(buf
, 0, buf
.length
)) > 0) {
99 fileString
= new String(Base64
.encodeBytes(out
.toByteArray()));
106 * Convert the given Base64 representation of an image into an {@link Image}
110 * the {@link Image} in Base64 format
112 * @return the {@link Image} object
114 * @throws IOException
115 * in case of IO error
117 static public BufferedImage
fromBase64(String b64data
) throws IOException
{
118 ByteArrayInputStream in
= new ByteArrayInputStream(
119 Base64
.decode(b64data
));
120 return fromStream(in
);
124 * Convert the given {@link InputStream} (which should allow calls to
125 * {@link InputStream#reset()} for better perfs) into an {@link Image}
126 * object, respecting the EXIF transformations if any.
129 * the 'resetable' {@link InputStream}
131 * @return the {@link Image} object
133 * @throws IOException
134 * in case of IO error
136 static public BufferedImage
fromStream(InputStream in
) throws IOException
{
137 MarkableFileInputStream tmpIn
= null;
141 } catch (IOException e
) {
142 tmp
= File
.createTempFile(".tmp-image", ".tmp");
144 IOUtils
.write(in
, tmp
);
145 tmpIn
= new MarkableFileInputStream(new FileInputStream(tmp
));
150 orientation
= getExifTransorm(in
);
151 } catch (Exception e
) {
152 // no EXIF transform, ok
157 BufferedImage image
= ImageIO
.read(in
);
164 throw new IOException("Failed to convert input to image");
167 // Note: this code has been found on Internet;
168 // thank you anonymous coder.
169 int width
= image
.getWidth();
170 int height
= image
.getHeight();
171 AffineTransform affineTransform
= new AffineTransform();
173 switch (orientation
) {
175 affineTransform
= null;
178 affineTransform
.scale(-1.0, 1.0);
179 affineTransform
.translate(-width
, 0);
181 case 3: // PI rotation
182 affineTransform
.translate(width
, height
);
183 affineTransform
.rotate(Math
.PI
);
186 affineTransform
.scale(1.0, -1.0);
187 affineTransform
.translate(0, -height
);
189 case 5: // - PI/2 and Flip X
190 affineTransform
.rotate(-Math
.PI
/ 2);
191 affineTransform
.scale(-1.0, 1.0);
193 case 6: // -PI/2 and -width
194 affineTransform
.translate(height
, 0);
195 affineTransform
.rotate(Math
.PI
/ 2);
197 case 7: // PI/2 and Flip
198 affineTransform
.scale(-1.0, 1.0);
199 affineTransform
.translate(-height
, 0);
200 affineTransform
.translate(0, width
);
201 affineTransform
.rotate(3 * Math
.PI
/ 2);
204 affineTransform
.translate(0, width
);
205 affineTransform
.rotate(3 * Math
.PI
/ 2);
208 affineTransform
= null;
212 if (affineTransform
!= null) {
213 AffineTransformOp affineTransformOp
= new AffineTransformOp(
214 affineTransform
, AffineTransformOp
.TYPE_BILINEAR
);
216 BufferedImage transformedImage
= new BufferedImage(width
, height
,
218 transformedImage
= affineTransformOp
219 .filter(image
, transformedImage
);
221 image
= transformedImage
;
234 * A shorthand method to create an {@link ImageText} and return its output.
237 * the source {@link Image}
239 * the final text size to target
241 * the mode of conversion
243 * TRUE to invert colours rendering
245 * @return the text image
247 static public String
toAscii(Image image
, Dimension size
, Mode mode
,
249 return new ImageText(image
, size
, mode
, invert
).toString();
253 * Return the EXIF transformation flag of this image if any.
256 * Note: this code has been found on internet; thank you anonymous coder.
260 * the data {@link InputStream}
262 * @return the transformation flag if any
264 * @throws IOException
265 * in case of IO error
267 static private int getExifTransorm(InputStream in
) throws IOException
{
268 int[] exif_data
= new int[100];
272 /* Read File head, check for JPEG SOI + Exif APP1 */
273 for (int i
= 0; i
< 4; i
++)
274 exif_data
[i
] = in
.read();
276 if (exif_data
[0] != 0xFF || exif_data
[1] != 0xD8
277 || exif_data
[2] != 0xFF || exif_data
[3] != 0xE1)
280 /* Get the marker parameter length count */
281 int length
= (in
.read() << 8 | in
.read());
283 /* Length includes itself, so must be at least 2 */
284 /* Following Exif data length must be at least 6 */
288 /* Read Exif head, check for "Exif" */
289 for (int i
= 0; i
< 6; i
++)
290 exif_data
[i
] = in
.read();
292 if (exif_data
[0] != 0x45 || exif_data
[1] != 0x78
293 || exif_data
[2] != 0x69 || exif_data
[3] != 0x66
294 || exif_data
[4] != 0 || exif_data
[5] != 0)
298 length
= length
> exif_data
.length ? exif_data
.length
: length
;
299 for (int i
= 0; i
< length
; i
++)
300 exif_data
[i
] = in
.read();
303 return -1; /* Length of an IFD entry */
305 /* Discover byte order */
306 if (exif_data
[0] == 0x49 && exif_data
[1] == 0x49)
308 else if (exif_data
[0] == 0x4D && exif_data
[1] == 0x4D)
314 if (is_motorola
== 1) {
315 if (exif_data
[2] != 0)
317 if (exif_data
[3] != 0x2A)
320 if (exif_data
[3] != 0)
322 if (exif_data
[2] != 0x2A)
326 /* Get first IFD offset (offset to IFD0) */
328 if (is_motorola
== 1) {
329 if (exif_data
[4] != 0)
331 if (exif_data
[5] != 0)
333 offset
= exif_data
[6];
335 offset
+= exif_data
[7];
337 if (exif_data
[7] != 0)
339 if (exif_data
[6] != 0)
341 offset
= exif_data
[5];
343 offset
+= exif_data
[4];
345 if (offset
> length
- 2)
346 return -1; /* check end of data segment */
348 /* Get the number of directory entries contained in this IFD */
350 if (is_motorola
== 1) {
351 number_of_tags
= exif_data
[offset
];
352 number_of_tags
<<= 8;
353 number_of_tags
+= exif_data
[offset
+ 1];
355 number_of_tags
= exif_data
[offset
+ 1];
356 number_of_tags
<<= 8;
357 number_of_tags
+= exif_data
[offset
];
359 if (number_of_tags
== 0)
363 /* Search for Orientation Tag in IFD0 */
365 if (offset
> length
- 12)
366 return -1; /* check end of data segment */
369 if (is_motorola
== 1) {
370 tagnum
= exif_data
[offset
];
372 tagnum
+= exif_data
[offset
+ 1];
374 tagnum
= exif_data
[offset
+ 1];
376 tagnum
+= exif_data
[offset
];
378 if (tagnum
== 0x0112)
379 break; /* found Orientation Tag */
380 if (--number_of_tags
== 0)
385 /* Get the Orientation value */
386 if (is_motorola
== 1) {
387 if (exif_data
[offset
+ 8] != 0)
389 set_flag
= exif_data
[offset
+ 9];
391 if (exif_data
[offset
+ 9] != 0)
393 set_flag
= exif_data
[offset
+ 8];