/** * Copyright (C) 2021, Tim Holloway * * Date written: Nov 26, 2021 * Author: Tim Holloway */ package com.mousetech.gourmetj.springweb; import java.awt.Color; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import javax.imageio.ImageIO; import javax.imageio.stream.ImageInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import com.mousetech.gourmetj.UserSession; import com.mousetech.gourmetj.persistence.model.Recipe; import com.mousetech.gourmetj.persistence.service.RecipeService; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; /** * @author timh * @since Nov 26, 2021 */ @Controller @RequestMapping(path="/img") public class PictureController { /* Logger */ private static final Logger log = LoggerFactory.getLogger(PictureController.class); @Autowired private RecipeService recipeService; /** * @return the recipeService */ public RecipeService getRecipeService() { return recipeService; } /** * @param recipeService the recipeService to set */ public void setRecipeService(RecipeService recipeService) { this.recipeService = recipeService; } // *** /** * Default image returned if a recipe has no actual image. * It's thumbnail-sized, so does double duty. */ static byte[] BLANK_IMAGE; public PictureController() { init(); } /** * General setup. Constructs BLANK_IMAGE. */ private void init() { BufferedImage tbi = new BufferedImage(40, 40, BufferedImage.TYPE_INT_ARGB); Graphics2D ig2 = tbi.createGraphics(); ig2.setBackground(Color.WHITE); ig2.clearRect(0, 0, 40, 40); try { ByteArrayOutputStream os = new ByteArrayOutputStream(946); ImageIO.write(tbi, "png", os); BLANK_IMAGE = os.toByteArray(); } catch (IOException e) { log.error("Unable to create BLANK_IMAGE", e); } } // ========================================== @GetMapping(value = "/picture/{recipeId}") @PostMapping(value = "/picture/{recipeId}") public ResponseEntity bigPicture( @PathVariable long recipeId) { Recipe recipe = recipeService.findByPrimaryKey(recipeId); if (recipe != null) { HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.set(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE); httpHeaders.set(HttpHeaders.CACHE_CONTROL, "no-cache"); byte[] image = recipe.getImage(); if (image == null) { image = BLANK_IMAGE; } return ResponseEntity.ok().headers(httpHeaders) .body(image); } // NotFound, need dummy picture!: return ResponseEntity.status(404).body(new byte[0]); } /** * Dummy picture request. Made when recipeId is not yet * assigned. * * @param req The HttpServletRequest from Spring. * @return Image response. */ @GetMapping(value = "/picture/") public ResponseEntity bigPicture( HttpServletRequest req) { final HttpSession httpSession = req.getSession(false); final UserSession userSession = (UserSession) httpSession .getAttribute("userSession"); Recipe editRecipe = userSession.getRecipe(); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.set(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE); httpHeaders.set(HttpHeaders.CACHE_CONTROL, "no-cache"); byte[] image = null; if ( editRecipe != null ) { // Otherwise we are creating new recipe, pre-display image = editRecipe.getImage(); } if (image == null) { image = BLANK_IMAGE; } return ResponseEntity.ok().headers(httpHeaders) .body(image); } @GetMapping(value = "/thumb/{recipeId}") public ResponseEntity thumbnail( @PathVariable long recipeId) { Recipe recipe = recipeService.findByPrimaryKey(recipeId); if (recipe != null) { HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.set(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE); byte[] image = recipe.getThumb(); // Empty thumbs have been seen with "H"! if (image == null || image.length < 2) { image = BLANK_IMAGE; } return ResponseEntity.ok().headers(httpHeaders) .body(image); } // NotFound, need dummy picture!: return ResponseEntity.status(404).body(new byte[0]); } /** * Take imageFile input and store it as image for recipe. * Generate thumbnail * * @param recipe Recipe to store into. * @param bs Info about uploaded data. * * CalledFrom @see * RecipeDetailBean#ajaxUploadImage(AjaxBehaviorEvent * event) */ public static void importImage(Recipe recipe, byte[] bs) { try { byte[] bytes = null; InputStream istream =new ByteArrayInputStream(bs); ImageInputStream stream = ImageIO.createImageInputStream(istream); BufferedImage bi = ImageIO.read(stream); if ((bi.getWidth() > 512) || (bi.getWidth() > 512)) { bi = scaleImageSanely(bi); } ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(bi, "png", baos); bytes = baos.toByteArray(); recipe.setImage(bytes); final int thumbHeight = 40; float ratio = ((float) bi.getHeight()) / thumbHeight; int thumbWidth = (int) (bi.getWidth() / ratio); BufferedImage resizedImg = new BufferedImage(thumbWidth, thumbHeight, BufferedImage.TRANSLUCENT); // Create a device-independent object to draw the // resized image Graphics2D g2 = resizedImg.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); // Finally draw the source image in the Graphics2D // with the desired size. g2.drawImage(bi, 0, 0, thumbWidth, thumbHeight, null); // Disposes of this graphics context and releases any // system resources that it is using g2.dispose(); baos = new ByteArrayOutputStream(); ImageIO.write(resizedImg, "png", baos); bytes = baos.toByteArray(); recipe.setThumb(bytes); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * Reduce image so that width or height fit. * * @param bi * * @return */ private static BufferedImage scaleImageSanely( BufferedImage bi) { int w0 = bi.getWidth(); int h0 = bi.getHeight(); int w1 = w0; int h1 = h0; double ratio = 1.0; if (h0 > 512) { h1 = 512; ratio = ((double) h0) / ((double) h1); w1 = (int) (w0 / ratio); } if (w1 > 512) { w0 = w1; w1 = 512; ratio = ((double) w0) / ((double) w1); w1 = (int) (w0 / ratio); } BufferedImage resizedImg = new BufferedImage(w1, h1, BufferedImage.TRANSLUCENT); Graphics2D g2 = resizedImg.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g2.drawImage(bi, 0, 0, w1, h1, null); g2.dispose(); return resizedImg; } }