1 package be
.nikiroo
.utils
;
4 import java
.awt
.geom
.AffineTransform
;
5 import java
.awt
.image
.AffineTransformOp
;
6 import java
.awt
.image
.BufferedImage
;
7 import java
.io
.BufferedReader
;
9 import java
.io
.FileInputStream
;
10 import java
.io
.FileOutputStream
;
11 import java
.io
.FileReader
;
12 import java
.io
.FileWriter
;
13 import java
.io
.IOException
;
14 import java
.io
.InputStream
;
15 import java
.io
.OutputStream
;
16 import java
.util
.zip
.ZipEntry
;
17 import java
.util
.zip
.ZipOutputStream
;
19 import javax
.imageio
.ImageIO
;
22 * This class offer some utilities based around Streams.
26 public class IOUtils
{
28 * Write the data to the given {@link File}.
33 * the target {@link File}
36 * in case of I/O error
38 public static void write(InputStream in
, File target
) throws IOException
{
39 OutputStream out
= new FileOutputStream(target
);
48 * Write the data to the given {@link OutputStream}.
53 * the target {@link OutputStream}
56 * in case of I/O error
58 public static void write(InputStream in
, OutputStream out
)
60 byte buffer
[] = new byte[4069];
61 for (int len
= 0; (len
= in
.read(buffer
)) > 0;) {
62 out
.write(buffer
, 0, len
);
67 * Recursively Add a {@link File} (which can thus be a directory, too) to a
68 * {@link ZipOutputStream}.
73 * the path to prepend to the ZIP info before the actual
76 * the source {@link File} (which can be a directory)
78 * FALSE if we need to add a {@link ZipEntry} for base/target,
79 * TRUE to add it at the root of the ZIP
82 * in case of I/O error
84 public static void zip(ZipOutputStream zip
, String base
, File target
,
85 boolean targetIsRoot
) throws IOException
{
86 if (target
.isDirectory()) {
88 if (base
== null || base
.isEmpty()) {
89 base
= target
.getName();
91 base
+= "/" + target
.getName();
93 zip
.putNextEntry(new ZipEntry(base
+ "/"));
95 for (File file
: target
.listFiles()) {
96 zip(zip
, base
, file
, false);
99 if (base
== null || base
.isEmpty()) {
100 base
= target
.getName();
102 base
+= "/" + target
.getName();
104 zip
.putNextEntry(new ZipEntry(base
));
105 FileInputStream in
= new FileInputStream(target
);
107 IOUtils
.write(in
, zip
);
115 * Zip the given source into dest.
118 * the source {@link File} (which can be a directory)
120 * the destination <tt>.zip</tt> file
122 * FALSE if we need to add a {@link ZipEntry} for src, TRUE to
123 * add it at the root of the ZIP
125 * @throws IOException
126 * in case of I/O error
128 public static void zip(File src
, File dest
, boolean srcIsRoot
)
130 OutputStream out
= new FileOutputStream(dest
);
132 ZipOutputStream zip
= new ZipOutputStream(out
);
134 IOUtils
.zip(zip
, "", src
, srcIsRoot
);
144 * Write the {@link String} content to {@link File}.
147 * the directory where to write the {@link File}
149 * the {@link File} name
153 * @throws IOException
154 * in case of I/O error
156 public static void writeSmallFile(File dir
, String filename
, String content
)
162 FileWriter writerVersion
= new FileWriter(new File(dir
, filename
));
164 writerVersion
.write(content
);
166 writerVersion
.close();
171 * Read the whole {@link File} content into a {@link String}.
176 * @return the content
178 * @throws IOException
179 * in case of I/O error
181 public static String
readSmallFile(File file
) throws IOException
{
182 BufferedReader reader
= new BufferedReader(new FileReader(file
));
184 StringBuilder builder
= new StringBuilder();
185 for (String line
= reader
.readLine(); line
!= null; line
= reader
187 builder
.append(line
);
189 return builder
.toString();
196 * Recursively delete the given {@link File}, which may of course also be a
199 * Will silently continue in case of error.
202 * the target to delete
204 public static void deltree(File target
) {
205 File
[] files
= target
.listFiles();
207 for (File file
: files
) {
212 if (!target
.delete()) {
213 System
.err
.println("Cannot delete: " + target
.getAbsolutePath());
218 * Convert the given {@link InputStream} (which should allow calls to
219 * {@link InputStream#reset() for better perfs}) into an {@link Image}
220 * object, respecting the EXIF transformations if any.
223 * the 'resetable' {@link InputStream}
225 * @return the {@link Image} object
227 * @throws IOException
228 * in case of IO error
230 public static BufferedImage
toImage(InputStream in
) throws IOException
{
231 MarkableFileInputStream tmpIn
= null;
235 } catch (IOException e
) {
236 tmp
= File
.createTempFile("fanfic-tmp-image", ".tmp");
238 IOUtils
.write(in
, tmp
);
239 tmpIn
= new MarkableFileInputStream(new FileInputStream(tmp
));
244 orientation
= getExifTransorm(in
);
245 } catch (Exception e
) {
246 // no EXIF transform, ok
251 BufferedImage image
= ImageIO
.read(in
);
258 throw new IOException("Failed to convert input to image");
261 // Note: this code has been found on internet;
262 // thank you anonymous coder.
263 int width
= image
.getWidth();
264 int height
= image
.getHeight();
265 AffineTransform affineTransform
= new AffineTransform();
267 switch (orientation
) {
269 affineTransform
= null;
272 affineTransform
.scale(-1.0, 1.0);
273 affineTransform
.translate(-width
, 0);
275 case 3: // PI rotation
276 affineTransform
.translate(width
, height
);
277 affineTransform
.rotate(Math
.PI
);
280 affineTransform
.scale(1.0, -1.0);
281 affineTransform
.translate(0, -height
);
283 case 5: // - PI/2 and Flip X
284 affineTransform
.rotate(-Math
.PI
/ 2);
285 affineTransform
.scale(-1.0, 1.0);
287 case 6: // -PI/2 and -width
288 affineTransform
.translate(height
, 0);
289 affineTransform
.rotate(Math
.PI
/ 2);
291 case 7: // PI/2 and Flip
292 affineTransform
.scale(-1.0, 1.0);
293 affineTransform
.translate(-height
, 0);
294 affineTransform
.translate(0, width
);
295 affineTransform
.rotate(3 * Math
.PI
/ 2);
298 affineTransform
.translate(0, width
);
299 affineTransform
.rotate(3 * Math
.PI
/ 2);
302 affineTransform
= null;
306 if (affineTransform
!= null) {
307 AffineTransformOp affineTransformOp
= new AffineTransformOp(
308 affineTransform
, AffineTransformOp
.TYPE_BILINEAR
);
310 BufferedImage transformedImage
= new BufferedImage(width
, height
,
312 transformedImage
= affineTransformOp
313 .filter(image
, transformedImage
);
315 image
= transformedImage
;
328 * Open the given /-separated resource (from the binary root).
333 * @return the opened resource if found, NLL if not
335 public static InputStream
openResource(String name
) {
336 ClassLoader loader
= IOUtils
.class.getClassLoader();
337 if (loader
== null) {
338 loader
= ClassLoader
.getSystemClassLoader();
341 return loader
.getResourceAsStream(name
);
345 * Return the EXIF transformation flag of this image if any.
348 * Note: this code has been found on internet; thank you anonymous coder.
352 * the data {@link InputStream}
354 * @return the transformation flag if any
356 * @throws IOException
357 * in case of IO error
359 private static int getExifTransorm(InputStream in
) throws IOException
{
360 int[] exif_data
= new int[100];
364 /* Read File head, check for JPEG SOI + Exif APP1 */
365 for (int i
= 0; i
< 4; i
++)
366 exif_data
[i
] = in
.read();
368 if (exif_data
[0] != 0xFF || exif_data
[1] != 0xD8
369 || exif_data
[2] != 0xFF || exif_data
[3] != 0xE1)
372 /* Get the marker parameter length count */
373 int length
= (in
.read() << 8 | in
.read());
375 /* Length includes itself, so must be at least 2 */
376 /* Following Exif data length must be at least 6 */
380 /* Read Exif head, check for "Exif" */
381 for (int i
= 0; i
< 6; i
++)
382 exif_data
[i
] = in
.read();
384 if (exif_data
[0] != 0x45 || exif_data
[1] != 0x78
385 || exif_data
[2] != 0x69 || exif_data
[3] != 0x66
386 || exif_data
[4] != 0 || exif_data
[5] != 0)
390 length
= length
> exif_data
.length ? exif_data
.length
: length
;
391 for (int i
= 0; i
< length
; i
++)
392 exif_data
[i
] = in
.read();
395 return -1; /* Length of an IFD entry */
397 /* Discover byte order */
398 if (exif_data
[0] == 0x49 && exif_data
[1] == 0x49)
400 else if (exif_data
[0] == 0x4D && exif_data
[1] == 0x4D)
406 if (is_motorola
== 1) {
407 if (exif_data
[2] != 0)
409 if (exif_data
[3] != 0x2A)
412 if (exif_data
[3] != 0)
414 if (exif_data
[2] != 0x2A)
418 /* Get first IFD offset (offset to IFD0) */
420 if (is_motorola
== 1) {
421 if (exif_data
[4] != 0)
423 if (exif_data
[5] != 0)
425 offset
= exif_data
[6];
427 offset
+= exif_data
[7];
429 if (exif_data
[7] != 0)
431 if (exif_data
[6] != 0)
433 offset
= exif_data
[5];
435 offset
+= exif_data
[4];
437 if (offset
> length
- 2)
438 return -1; /* check end of data segment */
440 /* Get the number of directory entries contained in this IFD */
442 if (is_motorola
== 1) {
443 number_of_tags
= exif_data
[offset
];
444 number_of_tags
<<= 8;
445 number_of_tags
+= exif_data
[offset
+ 1];
447 number_of_tags
= exif_data
[offset
+ 1];
448 number_of_tags
<<= 8;
449 number_of_tags
+= exif_data
[offset
];
451 if (number_of_tags
== 0)
455 /* Search for Orientation Tag in IFD0 */
457 if (offset
> length
- 12)
458 return -1; /* check end of data segment */
461 if (is_motorola
== 1) {
462 tagnum
= exif_data
[offset
];
464 tagnum
+= exif_data
[offset
+ 1];
466 tagnum
= exif_data
[offset
+ 1];
468 tagnum
+= exif_data
[offset
];
470 if (tagnum
== 0x0112)
471 break; /* found Orientation Tag */
472 if (--number_of_tags
== 0)
477 /* Get the Orientation value */
478 if (is_motorola
== 1) {
479 if (exif_data
[offset
+ 8] != 0)
481 set_flag
= exif_data
[offset
+ 9];
483 if (exif_data
[offset
+ 9] != 0)
485 set_flag
= exif_data
[offset
+ 8];