/* * This file is part of gwap, an open platform for games with a purpose * * Copyright (C) 2013 * Project play4science * Lehr- und Forschungseinheit für Programmier- und Modellierungssprachen * Ludwig-Maximilians-Universität München * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package gwap.tools; import gwap.model.resource.ArtResource; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.imageio.ImageIO; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.jboss.seam.Component; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Install; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.intercept.BypassInterceptors; import org.jboss.seam.servlet.ContextualHttpServletRequest; /** * Responsible for storing and retrieving hashes for images. * * @author Fabian Kneißl */ @Name("imageAccessBean") @Scope(ScopeType.APPLICATION) @BypassInterceptors @Install public class ImageAccessBean extends AbstractAccessBean { private static final long serialVersionUID = 1L; private static final int MAX_IMAGE_SIZE=400; private static final int MIN_IMAGE_SIZE=100; private static ImageAccessBean instance; protected Map<String, Map<Integer, String>> resizeCache = new ConcurrentHashMap<String, Map<Integer, String>>(); @Override protected String hashToUrl(String hash) { String basePath = (String) Component.getInstance("internalBasePath"); return basePath+"seam/resource/image/"+hash+".jpg"; } public static ImageAccessBean getInstance() { if (instance == null) instance = (ImageAccessBean) Component.getInstance("imageAccessBean"); return instance; } private BufferedImage resizeImage(BufferedImage image, int width, int height) { BufferedImage tempimg = new BufferedImage(width, height, image.getType()); Graphics2D g = tempimg.createGraphics(); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g.drawImage(image, 0, 0, width, height, 0, 0, image.getWidth(), image.getHeight(), null); g.dispose(); return tempimg; } @Override public void getResource(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { new ContextualHttpServletRequest(request) { @Override public void process() throws ServletException, IOException { String hash = request.getRequestURI(); try { hash = hash.substring(hash.lastIndexOf("/")+1, hash.lastIndexOf(".jpg")); ImageAccessBean imageAccess = ImageAccessBean.getInstance(); String imagePath = imageAccess.getPath(hash); if (imagePath==null) log.info("Image #0 not found in image list", hash); String imageDirectory = imagePath.substring(0, imagePath.lastIndexOf("/")); log.debug("Getting image #0 -> #1", hash, imagePath); String resizeParameter=request.getParameter("size"); if (resizeParameter!=null) { log.debug("ResizeParameter found, looking up image #0 in cache", imagePath); String resizedImagePath=null; //Resize image try { Integer size=Integer.parseInt(resizeParameter); if (size>MAX_IMAGE_SIZE) size=MAX_IMAGE_SIZE; if (size<MIN_IMAGE_SIZE) size=MIN_IMAGE_SIZE; //Only allow steps of 100 pixels size=((int)(size/100))*100; //Try to find image in cache Map<Integer, String> imageCache=resizeCache.get(imagePath); if (imageCache!=null) { resizedImagePath=imageCache.get(size); //Rebuild cache if files are missing if (resizedImagePath!=null && !new File(resizedImagePath).exists()) resizedImagePath=null; else log.debug("Found image #0 in cache: #1", imagePath, resizedImagePath); } //Resize image if (resizedImagePath==null) { log.debug("Resizing image #0", imagePath); BufferedImage image=ImageIO.read(new File(imagePath)); double aspect=(double)image.getWidth()/image.getHeight(); //Resize the image so that the shorter side is "size" pixels long if (image.getHeight() < image.getWidth()) image=resizeImage(image, (int)(size*aspect), size); else image=resizeImage(image, size, (int)(size/aspect)); log.debug("Image #0 resized", imagePath); String cachePath=imageDirectory+"/CACHE"; //Create Cache directory if necessary File cacheDirectory = new File(cachePath); if (!cacheDirectory.exists()) { log.debug("Trying to create directory #0", cachePath); cacheDirectory.mkdir(); } //Write image to disk resizedImagePath=cachePath+"/"+imagePath.substring(imagePath.lastIndexOf("/")+1, imagePath.lastIndexOf(".jpg"))+"_"+size.toString()+".jpg"; ImageIO.write(image, "jpg", new File(resizedImagePath)); log.debug("Image #0 written to cache #1", imagePath, resizedImagePath); //Put image into cache imageCache=resizeCache.get(imagePath); if (imageCache==null) { imageCache=new ConcurrentHashMap<Integer, String>(); resizeCache.put(imagePath, imageCache); } imageCache.put(size, resizedImagePath); log.debug("Image #0 added to cache", imagePath); } imagePath=resizedImagePath; } catch (Exception e) { log.info("Error resizing image: #0", e.getMessage()); } } response.setContentType("image/jpeg"); response.setHeader("Content-Disposition", "inline; filename=\"image.jpg\""); // response.setHeader("Cache-Control", "no-store"); // browser should use no cache // response.setHeader("Pragma", "no-cache"); // do not save on proxy server response.setDateHeader("Expires", URL_LIFETIME_SECS); log.info("Getting resource: #0 -> #1", hash, imagePath); InputStream in = new FileInputStream(imagePath); OutputStream out = response.getOutputStream(); log.debug("Writing stream: #0 -> outputStream", imagePath); int nrBytes = writeTo(in, out); log.debug("#0 bytes written", nrBytes); out.flush(); response.setHeader("Content-Length", ""+nrBytes); out.close(); } catch (Exception e) { log.info("getResource(#0): Could not load image: #1 (#2)", hash, e.getMessage(), e.getClass().getName()); response.sendError(404); } } }.run(); } @Override public String getResourcePath() { return "/image"; } public static void setResourceUrl(ArtResource artResource) { artResource.setUrl(ImageAccessBean.getInstance().createUrl(artResource.getSource().getUrl() + artResource.getPath())); } public static String getResourceUrl(String filePath) { return ImageAccessBean.getInstance().createUrl(filePath); } }