View Javadoc
1   /******************************************************************************
2    * PicReader.java - Process Pics into useful BufferedImages
3    * 
4    * PicMan - The BuckoSoft Picture Manager in Java
5    * Copyright(c) 2005 - Dick Balaska
6    * 
7    */
8   package com.buckosoft.PicMan.image;
9   
10  import java.awt.BasicStroke;
11  import java.awt.Color;
12  import java.awt.Dimension;
13  import java.awt.Graphics2D;
14  import java.awt.Image;
15  import java.awt.geom.AffineTransform;
16  import java.awt.image.AffineTransformOp;
17  import java.awt.image.BufferedImage;
18  import java.io.File;
19  import java.util.Iterator;
20  import java.util.concurrent.locks.ReentrantLock;
21  
22  import javax.imageio.ImageIO;
23  import javax.imageio.ImageReader;
24  import javax.imageio.stream.ImageInputStream;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  
29  import com.buckosoft.PicMan.business.PicManFacade;
30  import com.buckosoft.PicMan.domain.Pic;
31  import com.buckosoft.PicMan.domain.Thumbnail;
32  import com.drew.imaging.ImageMetadataReader;
33  import com.drew.metadata.Metadata;
34  import com.drew.metadata.exif.ExifIFD0Directory;
35  
36  /** Process Pics into useful BufferedImages
37   * @author Dick Balaska
38   * @since 2005/08/01
39   * @see <a href="http://cvs.buckosoft.com/Projects/PicMan/PicMan/src/main/java/com/buckosoft/PicMan/image/PicReader.java">PicReader.java</a>
40   */
41  public class PicReader {
42  
43  	private	PicManFacade	pmf;
44  	private	ThumbCache		thumbCache;
45  	private QueueDepth queueDepth = new QueueDepth();
46  	private	ReentrantLock	lock = new ReentrantLock();
47  	
48  	
49  	private	static final boolean DEBUG = true;
50  	protected final Log log = LogFactory.getLog(getClass());
51  
52  	public	void setPicMan(PicManFacade pmf) {
53  		this.pmf = pmf;
54  	}
55  	
56  	/** Set the reference to the ThumbCache manager
57  	 * @param thumbCache The thumbcache
58  	 */
59  	public	void	setThumbCache(ThumbCache thumbCache) {
60  		this.thumbCache = thumbCache;
61  	}
62  	
63  	public	PicManFacade getPicMan() { return(pmf); }
64  	
65  	/** Return how many people are waiting to read a pic 
66  	 * @return The depth
67  	 */
68  	public	int	getQueueDepth() { return(queueDepth.getDepth()); }
69  	
70  	public	 Thumbnail	getThumbNail(Pic pic, int height, String label) {
71  		Thumbnail tn = null;
72  		if (pic == null)
73  			return(getXThumb(height));
74  //		if (label == null)
75  		tn = thumbCache.getThumbNail(pic, height);
76  		if (tn == null) {
77  			enterQueue();
78  			try {
79  				tn = readThumbNail(pic, height, label);
80  			} catch (Throwable t) {
81  				exitQueue();
82  				RuntimeException e1 = new RuntimeException("Failed to read ThumbNail for \"" + pic.getName() + "\"", t);
83  				throw(e1);
84  			}
85  			exitQueue();
86  		}
87  		if (label != null) {
88  			int xp = 0;
89  			int yp = 0+tn.getImage().getHeight() - 1;
90  			Graphics2D g = tn.getImage().createGraphics();
91  			g.setColor(Color.BLACK);
92  			g.drawString(label, xp+1, yp+1);
93  			g.setColor(Color.WHITE);
94  			g.drawString(label, xp,   yp);
95  		}
96  		return(tn);
97  	}
98  
99  	/** Get the number of pictures files in a directory.
100 	 * This method doesn't really belong here, but this is the only module 
101 	 * that deals with files/pics in directories.
102 	 * @param rid The Root id to query
103 	 * @param dir The directory to read. i.e. "2003/20030428"
104 	 * @return the count
105 	 */
106 	public int getFilesInDirCount(int rid, String dir) {
107 		String fullPath = pmf.getDB().getRoot(rid).getPath() + "/" + dir;
108 		File f = new File(fullPath);
109 		String list[] = f.list();
110 		int count = 0;
111 		int i;
112 		if (list == null)
113 			return(0);
114 		for (i=0; i<list.length; i++) {
115 			if (list[i].endsWith(".jpg"))
116 				count++;
117 		}
118 		return(count);
119 	}
120 	
121 	/** Read a file and create a thumbnail
122 	 * @param pic
123 	 * @param thumbHeight
124 	 * @return
125 	 */
126 	private Thumbnail readThumbNail(Pic pic, int thumbHeight, String label) {
127 		String fullPath;
128 		fullPath = getFullPath(pmf, pic);
129 		if (DEBUG)
130 			log.info("Read: " + pic.getPid() + ": " + fullPath);
131 		Thumbnail tn;
132 		// XXX: Need to determine file extension, not whack a ".jpg" on the end.
133 		File f = null;
134 		try {
135 			f = new File(fullPath);
136 		} catch (Exception e) {
137 			return(getXThumb(thumbHeight));
138 		}
139 		Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("jpg");
140 		ImageReader reader = (ImageReader)readers.next();
141 		BufferedImage bi;
142 		Graphics2D	g;
143 		if (DEBUG)
144 			log.debug("Trying to read f=" + f);
145 		ImageInputStream iis = null;
146 		try {
147 			iis = ImageIO.createImageInputStream(f);
148 			if (DEBUG)
149 				log.debug("iis = " + iis);
150 			if (iis == null) {
151 				return(getXThumb(thumbHeight));
152 			}
153 			reader.setInput(iis, true);
154 			bi = reader.read(0);
155 			iis.close();
156 		} catch (IllegalStateException ise) {
157 			if (DEBUG)
158 				log.info("caught IllegalStateException");
159 			reader.dispose();
160 			bi = null;
161 			if (iis != null) {
162 				try {
163 					iis.close();
164 				} catch (Exception e) {}
165 			}
166 			return(getXThumb(thumbHeight));
167 		} catch (Exception e) {
168 			//if (DEBUG)
169 				log.info("caught Exception");
170 			reader.dispose();
171 			bi = null;
172 			if (iis != null) {
173 				try {
174 					iis.close();
175 				} catch (Exception ex) {}
176 			}
177 			return(getXThumb(thumbHeight));
178 		}
179 		reader.dispose();
180 		Metadata metadata = null;
181 		try {
182 			metadata = ImageMetadataReader.readMetadata(f);
183 //		} catch (ImageProcessingException | IOException e) {
184 		} catch (Exception e) {
185 			// TODO Auto-generated catch block
186 			e.printStackTrace();
187 		}
188 		log.debug("readThumbNail: File " + fullPath);
189 		ExifIFD0Directory directory = metadata.getDirectory(ExifIFD0Directory.class);
190 		String s = "--";
191 		try {
192 			s = directory.getString(ExifIFD0Directory.TAG_ORIENTATION);
193 		} catch (Exception e) {}
194 		int rot = 0;
195 		try {
196 			rot = directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
197 		} catch (Exception e) {
198 			//e.printStackTrace();
199 		}
200 		log.debug("   Orientation: '" + s + "' rot: " + rot);
201 /*		for (Directory directory : metadata.getDirectories()) {
202 		    for (Tag tag : directory.getTags()) {
203 		        logger.info(tag);
204 		    }
205 		}
206 */	
207 //		int	thumbHeight = pmf.getThumbHeight();
208 		int	width = bi.getWidth();
209 		int height = bi.getHeight();
210 		
211 		double	dW = ((double)thumbHeight/(double)height) * width;
212 		double	dH = ((double)thumbHeight/(double)width) * height;
213 		int newW = (int)dW;
214 		int newH = thumbHeight;
215 		// logger.info("newW/H = " + newW + " / " + newH );
216 		BufferedImage small;
217 		if (rot == 6) {
218 			log.debug("******* rotating 90");
219 			small = new BufferedImage((int)dH, thumbHeight, BufferedImage.TYPE_INT_BGR);
220 			BufferedImage bix;
221 			bix = new BufferedImage(bi.getHeight(), bi.getWidth(), BufferedImage.TYPE_INT_BGR);
222 			g = bix.createGraphics();
223 			AffineTransform trans = new AffineTransform(0.0, 1.0, -1.0, 0.0, bi.getHeight(), 0.0);
224 			g.transform(trans);
225 			g.drawImage(bi, 0, 0, bi.getWidth(), bi.getHeight(), null);
226 			bi = bix;		
227 			g = small.createGraphics();
228 			g.drawImage(bix.getScaledInstance(-1, thumbHeight, Image.SCALE_SMOOTH), 0, 0, small.getWidth(), small.getHeight(), null);
229 		} else if (rot == 3) {
230 			log.debug("******* rotating 180");
231 			small = new BufferedImage((int)dW, thumbHeight, BufferedImage.TYPE_INT_BGR);
232 			BufferedImage bix;
233 			bix = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_INT_BGR);
234 			//g = bix.createGraphics();
235 			AffineTransform trans = AffineTransform.getScaleInstance(-1, -1);
236 			trans.translate(-bix.getWidth(), -bix.getHeight());
237 			AffineTransformOp op = new AffineTransformOp(trans, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
238 			bix = op.filter(bi, null);
239 			g = small.createGraphics();
240 			g.drawImage(bix.getScaledInstance(-1, thumbHeight, Image.SCALE_SMOOTH), 0, 0, small.getWidth(), small.getHeight(), null);
241 		} else if (rot == 8) {
242 			log.debug("******* rotating -90");
243 			small = new BufferedImage((int)dH, thumbHeight, BufferedImage.TYPE_INT_BGR);
244 			BufferedImage bix;
245 			bix = new BufferedImage(bi.getHeight(), bi.getWidth(), BufferedImage.TYPE_INT_BGR);
246 			g = bix.createGraphics();
247 			AffineTransform trans = new AffineTransform(0.0, 1.0, -1.0, 0.0, bi.getHeight(), 0.0);
248 			g.transform(trans);
249 			g.drawImage(bi, 0, 0, bi.getWidth(), bi.getHeight(), null);
250 			bi = bix;		
251 			bix = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_INT_BGR);
252 			//g = bix.createGraphics();
253 			trans = AffineTransform.getScaleInstance(-1, -1);
254 			trans.translate(-bix.getWidth(), -bix.getHeight());
255 			AffineTransformOp op = new AffineTransformOp(trans, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
256 			bix = op.filter(bi, null);
257 			g = small.createGraphics();
258 			g.drawImage(bix.getScaledInstance(-1, thumbHeight, Image.SCALE_SMOOTH), 0, 0, small.getWidth(), small.getHeight(), null);
259 			
260 		} else {
261 			small = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_BGR);
262 			g = small.createGraphics();
263 			g.drawImage(bi.getScaledInstance(-1, newH, Image.SCALE_SMOOTH), null, null);
264 		}
265 		tn = new Thumbnail();
266 		tn.setName(pic.getName());
267 		tn.setImage(small);
268 		g = null;
269 		bi = null;
270 		if (!pic.isImportPic())
271 			thumbCache.addToCache(pic, tn);
272 		return(tn);
273 	}
274 	
275 	public	Thumbnail getXThumb(int thumbHeight) {
276 		int z = thumbHeight;
277 		Thumbnail tn = new Thumbnail();
278 		BufferedImage small = new BufferedImage(z, z, BufferedImage.TYPE_INT_BGR);
279 		Graphics2D g = small.createGraphics();
280 		g.setBackground(Color.BLACK);
281 		g.setColor(Color.RED);
282 //		java.awt.geom.Rectangle2D.Double rd = new java.awt.geom.Rectangle2D.Double(-0.5, -0.5, 1, 1);
283 		g.setStroke(new BasicStroke((float)5.0));
284 		g.drawLine(0, 0, z, z);
285 		g.drawLine(0, z, z, 0);
286 		tn.setImage(small);
287 		tn.setXThumb(true);
288 		return(tn);
289 	}
290 	
291 	public static String getFullPath(PicManFacade pmf, Pic pic) {
292 		String fullPath;
293 		if (pic.isImportPic())
294 			fullPath = pmf.getDB().getSystem().getImportDirectory() + "/" + pic.getName();
295 		else
296 			fullPath = pmf.getDB().getRoot(pic.getRid()).getPath() + "/" + pic.getLocation()
297 						+ "/" + pic.getName() + ".jpg";
298 		return(fullPath);
299 	}
300 
301 	private	void enterQueue() {
302 		queueDepth.incDepth();
303 		log.info("Enter New queue depth " + queueDepth.getDepth());
304 		lock.lock();
305 	}
306 	private	void exitQueue() {
307 		lock.unlock();
308 		queueDepth.decDepth();
309 		log.info("Exit  New queue depth " + queueDepth.getDepth());		
310 	}
311 
312 	
313 	public Dimension determinePicSize(Pic pic) {
314 		BufferedImage bi = readPic(pic);
315 		if (bi == null)
316 			return(new Dimension(-1, -1));
317 		return(new Dimension(bi.getWidth(), bi.getHeight()));
318 		
319 	}
320 
321 	public BufferedImage readPic(Pic pic) {
322 //		return(readPic(pic, null, 1.0));
323 		try {
324 			return(readPic(getFullPath(pmf, pic)));
325 		} catch (Exception e) {
326 			e.printStackTrace();
327 			return(null);
328 		}
329 	}
330 
331 	public BufferedImage readPic(String path) {
332 		File f = null;
333 		try {
334 			f = new File(path);
335 		} catch (Exception e) {
336 			return(null);
337 		}
338 		Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("jpg");
339 		ImageReader reader = (ImageReader)readers.next();
340 		BufferedImage bi;
341 		if (DEBUG)
342 			log.info("readPic: " + f);
343 		ImageInputStream iis = null;
344 		try {
345 			iis = ImageIO.createImageInputStream(f);
346 			//if (DEBUG)
347 			//	log.info("iis = " + iis);
348 			if (iis == null) {
349 				return(null);
350 			}
351 			reader.setInput(iis, true);
352 			bi = reader.read(0);
353 			iis.close();
354 		} catch (IllegalStateException ise) {
355 			if (DEBUG)
356 				log.info("caught IllegalStateException");
357 			reader.dispose();
358 			bi = null;
359 			if (iis != null) {
360 				try {
361 					iis.close();
362 				} catch (Exception ex) {}
363 			}
364 			return(null);
365 		} catch (Exception e) {
366 			if (DEBUG)
367 				log.info("caught Exception");
368 			reader.dispose();
369 			bi = null;
370 			if (iis != null) {
371 				try {
372 					iis.close();
373 				} catch (Exception ex) {}
374 			}
375 			return(null);
376 		}
377 		reader.dispose();
378 		Metadata metadata = null;
379 		try {
380 			metadata = ImageMetadataReader.readMetadata(f);
381 //		} catch (ImageProcessingException | IOException e) {
382 		} catch (Throwable t) {
383 			t.printStackTrace();
384 		}
385 		log.debug("readPic: File " + path);
386 		int rot = 0;
387 		String s = "--";
388 		if (metadata != null) {
389 			ExifIFD0Directory directory = metadata.getDirectory(ExifIFD0Directory.class);
390 			try {
391 				s = directory.getString(ExifIFD0Directory.TAG_ORIENTATION);
392 			} catch (Exception e) {}
393 			try {
394 				rot = directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
395 			} catch (Exception e) {
396 				log.debug("failed directory.getInt(ExifIFD0Directory.TAG_ORIENTATION) " + e.getLocalizedMessage());
397 			}
398 		}
399 		log.debug("   Orientation: '" + s + "' rot: " + rot);
400 		if (rot == 6 || rot == 8) {
401 			log.info("******* rotating 90 ********");
402 			BufferedImage bix;
403 			bix = new BufferedImage(bi.getHeight(), bi.getWidth(), BufferedImage.TYPE_INT_BGR);
404 			Graphics2D g = (Graphics2D)bix.createGraphics();
405 			AffineTransform trans = new AffineTransform(0.0, 1.0, -1.0, 0.0, bi.getHeight(), 0.0);
406 			g.transform(trans);
407 			g.drawImage(bi, 0, 0, bi.getWidth(), bi.getHeight(), null);
408 			bi = bix;		
409 /*		} else if (rot == 8) {
410 			log.info("******* rotating -90 ********");
411 			BufferedImage bix;
412 			bix = new BufferedImage(bi.getHeight(), bi.getWidth(), BufferedImage.TYPE_INT_BGR);
413 			Graphics2D g = (Graphics2D)bix.createGraphics();
414 			AffineTransform trans = new AffineTransform(0.0, 1.0, -1.0, 0.0, bi.getHeight(), 0.0);
415 			g.transform(trans);
416 			g.drawImage(bi, 0, 0, bi.getWidth(), bi.getHeight(), null);
417 			bi = bix;		
418 */		}
419 		 if (rot == 3 || rot == 8) {
420 			log.debug("******* rotating 180 ");
421 			BufferedImage bix;
422 			bix = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_INT_BGR);
423 			AffineTransform trans = AffineTransform.getScaleInstance(-1, -1);
424 			trans.translate(-bix.getWidth(), -bix.getHeight());
425 			AffineTransformOp op = new AffineTransformOp(trans, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
426 			bix = op.filter(bi, null);
427 			bi = bix;		
428 		}
429 		return(bi);
430 	}
431 	private class QueueDepth {
432 		private int queueDepth = 0;
433 		
434 		public void incDepth() {
435 			synchronized(this) {
436 				queueDepth++;
437 			}
438 		}
439 		public void decDepth() {
440 			synchronized(this) {
441 				queueDepth--;
442 			}
443 		}
444 		public int getDepth() {
445 			synchronized(this) {
446 				return(queueDepth);
447 			}
448 		}
449 	}
450 }