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 for (File file
: target
.listFiles()) {
206 if (file
.isDirectory()) {
209 if (!file
.delete()) {
210 System
.err
.println("Cannot delete file: "
211 + file
.getAbsolutePath());
216 if (!target
.delete()) {
217 System
.err
.println("Cannot delete file: "
218 + target
.getAbsolutePath());
223 * Convert the given {@link InputStream} (which should allow calls to
224 * {@link InputStream#reset() for better perfs}) into an {@link Image}
225 * object, respecting the EXIF transformations if any.
228 * the 'resetable' {@link InputStream}
230 * @return the {@link Image} object
232 * @throws IOException
233 * in case of IO error
235 public static BufferedImage
toImage(InputStream in
) throws IOException
{
236 MarkableFileInputStream tmpIn
= null;
240 } catch (IOException e
) {
241 tmp
= File
.createTempFile("fanfic-tmp-image", ".tmp");
243 IOUtils
.write(in
, tmp
);
244 tmpIn
= new MarkableFileInputStream(new FileInputStream(tmp
));
249 orientation
= getExifTransorm(in
);
250 } catch (Exception e
) {
251 // no EXIF transform, ok
256 BufferedImage image
= ImageIO
.read(in
);
263 throw new IOException("Failed to convert input to image");
266 // Note: this code has been found on internet;
267 // thank you anonymous coder.
268 int width
= image
.getWidth();
269 int height
= image
.getHeight();
270 AffineTransform affineTransform
= new AffineTransform();
272 switch (orientation
) {
274 affineTransform
= null;
277 affineTransform
.scale(-1.0, 1.0);
278 affineTransform
.translate(-width
, 0);
280 case 3: // PI rotation
281 affineTransform
.translate(width
, height
);
282 affineTransform
.rotate(Math
.PI
);
285 affineTransform
.scale(1.0, -1.0);
286 affineTransform
.translate(0, -height
);
288 case 5: // - PI/2 and Flip X
289 affineTransform
.rotate(-Math
.PI
/ 2);
290 affineTransform
.scale(-1.0, 1.0);
292 case 6: // -PI/2 and -width
293 affineTransform
.translate(height
, 0);
294 affineTransform
.rotate(Math
.PI
/ 2);
296 case 7: // PI/2 and Flip
297 affineTransform
.scale(-1.0, 1.0);
298 affineTransform
.translate(-height
, 0);
299 affineTransform
.translate(0, width
);
300 affineTransform
.rotate(3 * Math
.PI
/ 2);
303 affineTransform
.translate(0, width
);
304 affineTransform
.rotate(3 * Math
.PI
/ 2);
307 affineTransform
= null;
311 if (affineTransform
!= null) {
312 AffineTransformOp affineTransformOp
= new AffineTransformOp(
313 affineTransform
, AffineTransformOp
.TYPE_BILINEAR
);
315 BufferedImage transformedImage
= new BufferedImage(width
, height
,
317 transformedImage
= affineTransformOp
318 .filter(image
, transformedImage
);
320 image
= transformedImage
;
333 * Open the given /-separated resource (from the binary root).
338 * @return the opened resource if found, NLL if not
340 public static InputStream
openResource(String name
) {
341 ClassLoader loader
= IOUtils
.class.getClassLoader();
342 if (loader
== null) {
343 loader
= ClassLoader
.getSystemClassLoader();
346 return loader
.getResourceAsStream(name
);
350 * Return the EXIF transformation flag of this image if any.
353 * Note: this code has been found on internet; thank you anonymous coder.
357 * the data {@link InputStream}
359 * @return the transformation flag if any
361 * @throws IOException
362 * in case of IO error
364 private static int getExifTransorm(InputStream in
) throws IOException
{
365 int[] exif_data
= new int[100];
369 /* Read File head, check for JPEG SOI + Exif APP1 */
370 for (int i
= 0; i
< 4; i
++)
371 exif_data
[i
] = in
.read();
373 if (exif_data
[0] != 0xFF || exif_data
[1] != 0xD8
374 || exif_data
[2] != 0xFF || exif_data
[3] != 0xE1)
377 /* Get the marker parameter length count */
378 int length
= (in
.read() << 8 | in
.read());
380 /* Length includes itself, so must be at least 2 */
381 /* Following Exif data length must be at least 6 */
385 /* Read Exif head, check for "Exif" */
386 for (int i
= 0; i
< 6; i
++)
387 exif_data
[i
] = in
.read();
389 if (exif_data
[0] != 0x45 || exif_data
[1] != 0x78
390 || exif_data
[2] != 0x69 || exif_data
[3] != 0x66
391 || exif_data
[4] != 0 || exif_data
[5] != 0)
395 length
= length
> exif_data
.length ? exif_data
.length
: length
;
396 for (int i
= 0; i
< length
; i
++)
397 exif_data
[i
] = in
.read();
400 return -1; /* Length of an IFD entry */
402 /* Discover byte order */
403 if (exif_data
[0] == 0x49 && exif_data
[1] == 0x49)
405 else if (exif_data
[0] == 0x4D && exif_data
[1] == 0x4D)
411 if (is_motorola
== 1) {
412 if (exif_data
[2] != 0)
414 if (exif_data
[3] != 0x2A)
417 if (exif_data
[3] != 0)
419 if (exif_data
[2] != 0x2A)
423 /* Get first IFD offset (offset to IFD0) */
425 if (is_motorola
== 1) {
426 if (exif_data
[4] != 0)
428 if (exif_data
[5] != 0)
430 offset
= exif_data
[6];
432 offset
+= exif_data
[7];
434 if (exif_data
[7] != 0)
436 if (exif_data
[6] != 0)
438 offset
= exif_data
[5];
440 offset
+= exif_data
[4];
442 if (offset
> length
- 2)
443 return -1; /* check end of data segment */
445 /* Get the number of directory entries contained in this IFD */
447 if (is_motorola
== 1) {
448 number_of_tags
= exif_data
[offset
];
449 number_of_tags
<<= 8;
450 number_of_tags
+= exif_data
[offset
+ 1];
452 number_of_tags
= exif_data
[offset
+ 1];
453 number_of_tags
<<= 8;
454 number_of_tags
+= exif_data
[offset
];
456 if (number_of_tags
== 0)
460 /* Search for Orientation Tag in IFD0 */
462 if (offset
> length
- 12)
463 return -1; /* check end of data segment */
466 if (is_motorola
== 1) {
467 tagnum
= exif_data
[offset
];
469 tagnum
+= exif_data
[offset
+ 1];
471 tagnum
= exif_data
[offset
+ 1];
473 tagnum
+= exif_data
[offset
];
475 if (tagnum
== 0x0112)
476 break; /* found Orientation Tag */
477 if (--number_of_tags
== 0)
482 /* Get the Orientation value */
483 if (is_motorola
== 1) {
484 if (exif_data
[offset
+ 8] != 0)
486 set_flag
= exif_data
[offset
+ 9];
488 if (exif_data
[offset
+ 9] != 0)
490 set_flag
= exif_data
[offset
+ 8];