View Javadoc
1   /******************************************************************************
2    * ImageController.java - The Spring controller to fetch images
3    * 
4    * BuckoVidLib - The BuckoSoft Video Library
5    * Copyright(c) 2014 - Dick Balaska
6    * 
7    */
8   package com.buckosoft.BuckoVidLib.web;
9   
10  import java.awt.Color;
11  import java.awt.Font;
12  import java.awt.FontMetrics;
13  import java.awt.Graphics2D;
14  import java.awt.Image;
15  import java.awt.image.BufferedImage;
16  import java.io.File;
17  import java.io.IOException;
18  import java.io.OutputStream;
19  import java.net.MalformedURLException;
20  import java.net.URL;
21  import java.util.Enumeration;
22  import java.util.Iterator;
23  
24  import javax.imageio.ImageIO;
25  import javax.imageio.ImageReader;
26  import javax.imageio.ImageWriter;
27  import javax.imageio.stream.ImageInputStream;
28  import javax.imageio.stream.ImageOutputStream;
29  import javax.servlet.http.HttpServletRequest;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.springframework.beans.factory.annotation.Autowired;
34  import org.springframework.stereotype.Controller;
35  import org.springframework.web.bind.annotation.PathVariable;
36  import org.springframework.web.bind.annotation.RequestMapping;
37  
38  import com.buckosoft.BuckoVidLib.business.BuckoVidLib;
39  import com.buckosoft.BuckoVidLib.domain.VideoBase;
40  import com.buckosoft.BuckoVidLib.util.ConfigManager;
41  import com.buckosoft.BuckoVidLib.util.DomainConverter;
42  
43  /** The Spring controller to fetch images (background art)
44   * @author dick
45   * @since 2014-10-07
46   */
47  @Controller(value="image")
48  @RequestMapping("/image")
49  public class ImageController  {
50  	private final Log log = LogFactory.getLog(getClass());
51  	private	final String defaultBackground = "http://www.buckosoft.com/bvlib/static/defaultBackground.jpg";
52  	private	final String greyBackground = "http://www.buckosoft.com/bvlib/static/greyBackground.png";
53  	private	final String tteoacBackground = "http://www.buckosoft.com/bvlib/static/tteoacBackground.jpg";
54  
55  	/** Fit the image to the window */
56  	public final static int bgFit = 0;
57  	/** Fill the window with the image */
58  	public final static int bgFill = 1;
59  	/** Plain grey background */
60  	public final static int bgGrey = 2;
61  
62  	public final static String keyDefault = "0";
63  	public final static String keyNone = "1";
64  	public final static String keyTteoac = "2";
65  
66  	
67  	@Autowired
68  	private BuckoVidLib		bvl;
69  
70  	private	boolean	useImageCache = false;
71  	private	String	imageCacheBaseDir;
72  	
73  	/** Bean constructor - instantiated by Spring */
74  	public ImageController() {
75  		useImageCache = ConfigManager.getBoolean("BuckoVidLib.useImageCache", false);
76  		imageCacheBaseDir = ConfigManager.getString("BuckoVidLib.imageCacheBaseDir", "C:/configureme");
77  	}
78  
79  	
80  	/**
81  	 * @param useImageCache the useImageCache to set
82  	 */
83  	public void setUseImageCache(boolean useImageCache) {
84  		this.useImageCache = useImageCache;
85  	}
86  
87  
88  	/** Fetch a background image. <br>
89  	 * Session parameters:
90  	 * <ul>
91  	 * <li>backgroundFit - how do we scale the image?
92  	 * <li>allowRestricted - Are you hunting Horcruxes?
93  	 * </ul>
94  	 * Optional Url parameters
95  	 * <ul>
96  	 * <li>w = width of image
97  	 * <li>h = height of image
98  	 * <li>a = artShow override for the fit type
99  	 * </ul>
100 	 * @param key The base36 video key
101 	 * @param request The request that contains the session that contains the user
102 	 * @param response Write the image/jpeg
103 	 */
104 	@RequestMapping("/{key}")
105 	public void handleRequest(@PathVariable("key") String key,
106 			HttpServletRequest request, OutputStream outputStream) {
107 //		String s = "/library/metadata/3164/art/1412661793";	// holy grail background
108 //		String t = "/library/metadata/3133/art/1412371407";
109 //		URL url = new URL("http://lamp/plex" + (key.endsWith("k") ? s : t));
110 		int	w = 0;
111 		int	h = 0;
112 		String s;
113 		int backgroundFit = bgFit;
114 		boolean a = false;		// running ArtShow?
115 		s = request.getParameter("w");
116 		if (s != null)
117 			w = Integer.parseInt(s);
118 		s = request.getParameter("h");
119 		if (s != null)
120 			h = Integer.parseInt(s);
121 		s = request.getParameter("a");
122 		if (s != null)
123 			a = true;
124 		log.debug("w/h = " + w + "/" + h);
125 		if (w > 3000 || h > 3000 || w < 0 || h < 0) {
126 			log.warn("BADGUY! Limiting requested image size " + w + "/" + h);
127 			w = 400;
128 			h = 400;
129 		}
130 		backgroundFit = bvl.getBackgroundFit(request, a);
131 		if (log.isTraceEnabled()) {
132 			for (Enumeration<String> e = request.getHeaderNames(); e.hasMoreElements();) {
133 				String ee = e.nextElement();
134 				String ww = request.getHeader(ee);
135 				log.debug("Header '" + ee + "' : '" + ww + "'");
136 			}
137 			for (Enumeration<String> e = request.getParameterNames(); e.hasMoreElements();) {
138 				String ee = e.nextElement();
139 				String ww = request.getParameter(ee);
140 				log.debug("Param '" + ee + "' : '" + ww + "'");
141 			}
142 		}
143 		VideoBase v = null;
144 		String printedTitle = null;
145 		String	backgroundUrl;
146 		if (key.equals("0")) {
147 			backgroundUrl = defaultBackground;
148 			backgroundFit = bgFill;				// default image (DVD rack) always fills
149 		} else if (key.equals("1")) {
150 			backgroundUrl = greyBackground;
151 		} else if (key.equals("2")) {
152 			backgroundUrl = tteoacBackground;
153 			backgroundFit = bgFill;				// tteoac always fills
154 			
155 		} else {
156 			boolean b = bvl.isAllowRestricted(request);
157 			v = bvl.getVideoBaseFromKey(DomainConverter.keyToInt(key), b);
158 			if (v == null) {
159 				log.warn("Video not found for key: " + key);
160 				backgroundUrl = defaultBackground;
161 			} else {
162 				printedTitle = v.getTitle() + " (" + v.getYear() + ")";
163 				backgroundUrl = bvl.getPlexUrl() + v.getBackgroundUrl();
164 			}
165 		}
166 		BufferedImage img = null;
167 		if (useImageCache) {
168 			s = imageCacheBaseDir + "/art/" + key;
169 			File f = null;
170 			try {
171 				f = new File(s);
172 			} catch (Exception e) {
173 				log.warn("imageController: Can't create file '" + s + "'");
174 				return;
175 			}
176 //			Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("jpg");
177 //			ImageReader reader = (ImageReader)readers.next();
178 			ImageReader reader = null;
179 			ImageInputStream iis = null;
180 			try {
181 				iis = ImageIO.createImageInputStream(f);
182 				log.debug("iis = " + iis);
183 				if (iis == null) {
184 					log.warn("imageController: Can't read file '" + s + "'");
185 					return;
186 				}
187 				Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
188 				reader = (ImageReader)readers.next();
189 				reader.setInput(iis, true);
190 				img = reader.read(0);
191 				iis.close();
192 			} catch (Exception e) {
193 				log.warn(e);
194 				if (reader != null)
195 					reader.dispose();
196 				img = null;
197 				if (iis != null) {
198 					try {
199 						iis.close();
200 					} catch (Exception e1) {}
201 				}
202 				log.warn("imageController: Can't create image '" + s + "'");
203 				return;
204 			}
205 		} else {
206 			// read the image from plex
207 			URL url;
208 			try {
209 				url = new URL(backgroundUrl);
210 			} catch (MalformedURLException e2) {
211 				log.warn("Failed to convert '" + backgroundUrl + "'");
212 				return;
213 			}
214 			try {
215 				img = ImageIO.read(url);
216 			} catch (Exception e1) {
217 				log.warn("Can't load image for " + (v != null ? v.getTitle() : "null image"));
218 				return;
219 			}
220 		}
221 		log.info("Fetch background for: " + printedTitle);
222 		log.debug("original image: " + img.getWidth() + "/" + img.getHeight());
223 		log.debug("backgroundFit = " + backgroundFit);
224 		if (backgroundFit != bgFit) {									// handle fill
225 			if (w > 1 && h > 1) {
226 				int	targetw;
227 				int	targeth;
228 				double wp = (double)img.getWidth() / (double)w;
229 				double hp = (double)img.getHeight() / (double)h;
230 				if (hp < wp) {
231 					targetw = (int)((double)img.getWidth() / hp);
232 					targeth = h;
233 				} else {
234 					targeth = (int)((double)img.getHeight() / wp);
235 					targetw = w;
236 				}
237 				log.debug("grow target: " + targetw + "/" + targeth);
238 				BufferedImage scaled = new BufferedImage(targetw, targeth, BufferedImage.TYPE_INT_BGR);
239 				Graphics2D g;
240 				g = scaled.createGraphics();
241 				g.drawImage(img.getScaledInstance(targetw, targeth, Image.SCALE_SMOOTH), null, null);
242 				// replace outgoing image with scaled version
243 				img = scaled;
244 				g.dispose();
245 				// pick an exact sized slice out of the image
246 				int overw = img.getWidth() - w;
247 				int	overh = img.getHeight() - h;
248 				int shiftw = (int)(Math.random()*overw);
249 				int shifth = (int)(Math.random()*overh);
250 				if (key.equals(keyTteoac)) {
251 					shiftw = overw/2;
252 					shifth = 0;
253 				}
254 				log.debug("over: " + overw + "/" + overh + " shift: " + shiftw + "/" + shifth);
255 				img = img.getSubimage(shiftw, shifth, w-17, h-0);
256 			}
257 		} else {													// handle fit
258 			if (w > 1 && h > 1) {
259 				int	targetw;
260 				int	targeth;
261 				int x = 0;
262 				int y = 0;
263 				double wp = (double)w / (double)img.getWidth();
264 				double hp = (double)h / (double)img.getHeight();
265 				if (hp < wp) {
266 					targetw = (int)((double)img.getWidth() * hp);
267 					targeth = (int)((double)img.getHeight() * hp);
268 					x = w/2 - (targetw/2);
269 				} else {
270 					targeth = (int)((double)img.getHeight() * wp);
271 					targetw = (int)((double)img.getWidth() * wp);
272 					y = h/2 - (targeth/2);
273 				}
274 				log.debug("screen: " + w + " / " + h);
275 				log.debug("wp = " + wp + " hp = " + hp);
276 				log.debug("grow target: " + targetw + "/" + targeth);
277 				log.debug("x/y: " + x + " / " + y);
278 				BufferedImage scaled = new BufferedImage(targetw, targeth, BufferedImage.TYPE_INT_BGR);
279 				Graphics2D g;
280 				g = scaled.createGraphics();
281 				g.drawImage(img.getScaledInstance(targetw, targeth, Image.SCALE_SMOOTH), null, null);
282 				// replace outgoing image with scaled version
283 				img = scaled;
284 				img = new BufferedImage(w-17, h-1, BufferedImage.TYPE_INT_BGR);
285 				g = img.createGraphics();
286 				g.setColor(Color.gray);
287 				g.fillRect(0, 0, w, h);
288 				
289 				g.drawImage(scaled, null, x, y);
290 				g.dispose();
291 				scaled = null;
292 			}
293 		}
294 		// Add the title to the corner
295 		if (printedTitle != null && printedTitle.length() > 0) {
296 			Graphics2D g = img.createGraphics();
297 			Font font = new Font("SansSerif", Font.BOLD, 30);
298 			Font oldFont = g.getFont();
299 			g.setFont(font);
300 			FontMetrics fm = g.getFontMetrics(font);
301 			int hofs = h - fm.getHeight() - 0;		// Allow for the footer bar.
302 			int wofs = w - fm.stringWidth(printedTitle) - 32;
303 			g.setColor(Color.black);
304 			g.drawString(printedTitle, wofs, hofs);
305 			g.setColor(Color.white);
306 			g.drawString(printedTitle, wofs-2, hofs-2);
307 	//		g.setColor(Color.black);
308 	//		g.drawString(printedTitle, wofs-2, hofs-2);
309 	//		g.drawString(printedTitle, wofs+2, hofs+2);
310 	//		g.drawString(printedTitle, wofs-2, hofs+2);
311 	//		g.drawString(printedTitle, wofs+2, hofs-2);
312 	//		g.setColor(Color.white);
313 	//		g.drawString(printedTitle, wofs, hofs);
314 	
315 			g.setFont(oldFont);
316 			g.dispose();
317 		}
318 		// write the image to the stream
319 		try {
320 			ImageOutputStream ios = ImageIO.createImageOutputStream(outputStream);
321 			Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
322 			ImageWriter writer = (ImageWriter)writers.next();
323 			writer.setOutput(ios);
324 			writer.write(img);
325 		} catch (IOException e) {
326 			log.warn("Failed to write image " + backgroundUrl);
327 		}
328 		return;
329 	}
330 	
331 	/** Read an unfiltered background image from plex. <br>
332 	 * This is a slimmed down version of main imageReader, with no processing.
333 	 * It's used to build the cache
334 	 * @param key The bvlKey
335 	 * @param outputStream
336 	 * @return null on success, otherwise an error message
337 	 */
338 	public String handleRequest(String key, OutputStream outputStream) {
339 		VideoBase v = bvl.getVideoBaseFromKey(DomainConverter.keyToInt(key), true);
340 		String backgroundUrl = bvl.getPlexUrl() + v.getBackgroundUrl();
341 		BufferedImage img = null;
342 		URL url;
343 		String s;
344 		try {
345 			url = new URL(backgroundUrl);
346 		} catch (MalformedURLException e2) {
347 			s = "Failed to convert '" + backgroundUrl + "'";
348 			log.warn(s);
349 			return(s);
350 		}
351 		try {
352 			img = ImageIO.read(url);
353 		} catch (Exception e1) {
354 			s = "Can't load image for " + (v != null ? v.getTitle() : "null image");
355 			log.warn(s);
356 			return(s);
357 		}
358 		try {
359 			ImageOutputStream ios = ImageIO.createImageOutputStream(outputStream);
360 			Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
361 			ImageWriter writer = (ImageWriter)writers.next();
362 			writer.setOutput(ios);
363 			writer.write(img);
364 		} catch (IOException e) {
365 			s = "Failed to write image " + backgroundUrl;
366 			log.warn(s);
367 			return(s);
368 		}
369 		return(null);	
370 	}	
371 }