Thumbnails!
This commit is contained in:
		
							parent
							
								
									3083b2596c
								
							
						
					
					
						commit
						11f77cf995
					
				
							
								
								
									
										12
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								pom.xml
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -56,7 +56,17 @@
 | 
			
		|||
        <dependency>
 | 
			
		||||
            <groupId>javax.validation</groupId>
 | 
			
		||||
            <artifactId>validation-api</artifactId>
 | 
			
		||||
            <version>2.0.1.Final</version>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.hibernate.validator</groupId>
 | 
			
		||||
            <artifactId>hibernate-validator</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.springframework.boot</groupId>
 | 
			
		||||
            <artifactId>spring-boot-starter-web</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,6 +23,8 @@ import com.mousetech.gourmetj.persistence.model.Recipe;
 | 
			
		|||
public interface RecipeRepository
 | 
			
		||||
		extends JpaRepository<Recipe, Long> {
 | 
			
		||||
 | 
			
		||||
	List<Recipe> findByTitleContaining(String searchText);
 | 
			
		||||
 | 
			
		||||
//	final static String SQL_FIND_CATEGORIES =
 | 
			
		||||
//			"SELECT DISTINCT category from categories"
 | 
			
		||||
//					+ " where category is not null and category <> ''"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,8 @@ package com.mousetech.gourmetj.persistence.service;
 | 
			
		|||
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
import javax.enterprise.context.ApplicationScoped;
 | 
			
		||||
import javax.inject.Inject;
 | 
			
		||||
import javax.inject.Named;
 | 
			
		||||
| 
						 | 
				
			
			@ -33,8 +35,11 @@ public class RecipeService implements Serializable {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	public List<Recipe> findByTitle(String searchText) {
 | 
			
		||||
		// TODO Auto-generated method stub
 | 
			
		||||
		return null;
 | 
			
		||||
		return recipeRepository.findByTitleContaining(searchText);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public Recipe findByPrimaryKey(Long recipeId) {
 | 
			
		||||
		return recipeRepository.findById(recipeId).orElse(null);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
//  /**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,281 @@
 | 
			
		|||
/**
 | 
			
		||||
 * Copyright (C) 2021, Tim Holloway
 | 
			
		||||
 *
 | 
			
		||||
 * Date written: Nov 26, 2021
 | 
			
		||||
 * Author: Tim Holloway <timh@mousetech.com>
 | 
			
		||||
 */
 | 
			
		||||
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.ByteArrayOutputStream;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
 | 
			
		||||
import javax.imageio.ImageIO;
 | 
			
		||||
import javax.imageio.stream.ImageInputStream;
 | 
			
		||||
import javax.servlet.http.HttpServletRequest;
 | 
			
		||||
import javax.servlet.http.HttpSession;
 | 
			
		||||
import javax.servlet.http.Part;
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @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<byte[]> 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<byte[]> 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 = editRecipe.getImage();
 | 
			
		||||
		if (image == null) {
 | 
			
		||||
			image = BLANK_IMAGE;
 | 
			
		||||
		}
 | 
			
		||||
		return ResponseEntity.ok().headers(httpHeaders)
 | 
			
		||||
			.body(image);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@GetMapping(value = "/thumb/{recipeId}")
 | 
			
		||||
	public ResponseEntity<byte[]> 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 thumnail
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param recipe    Recipe to store into.
 | 
			
		||||
	 * @param imageFile Info about uploaded data.
 | 
			
		||||
	 * 
 | 
			
		||||
	 *                  CalledFrom @see
 | 
			
		||||
	 *                  RecipeDetailBean#ajaxUploadImage(AjaxBehaviorEvent
 | 
			
		||||
	 *                  event)
 | 
			
		||||
	 */
 | 
			
		||||
	public static void importImage(Recipe recipe,
 | 
			
		||||
			Part imageFile) {
 | 
			
		||||
		// imageFile.getContentType(); // ex: image/jpeg
 | 
			
		||||
		try {
 | 
			
		||||
			byte[] bytes = null;
 | 
			
		||||
 | 
			
		||||
			InputStream istream = imageFile.getInputStream();
 | 
			
		||||
			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;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
/**
 | 
			
		||||
 * Copyright (C) 2021, Tim Holloway
 | 
			
		||||
 *
 | 
			
		||||
 * Date written: Dec 28, 2021
 | 
			
		||||
 * Author: Tim Holloway <timh@mousetech.com>
 | 
			
		||||
 */
 | 
			
		||||
/**
 | 
			
		||||
 * @author timh
 | 
			
		||||
 * @since Dec 28, 2021
 | 
			
		||||
 */
 | 
			
		||||
package com.mousetech.gourmetj.springweb;
 | 
			
		||||
| 
						 | 
				
			
			@ -5,10 +5,14 @@
 | 
			
		|||
    xmlns:tc="http://myfaces.apache.org/tobago/component"
 | 
			
		||||
>
 | 
			
		||||
    <f:view>
 | 
			
		||||
            <h:head>
 | 
			
		||||
            <title><ui:insert name="title">Gourmet Recipe Manager (web version)</ui:insert></title>
 | 
			
		||||
            <link rel="icon" type="image/vnd.microsoft.icon"
 | 
			
		||||
                href="#{pageContext.contextPath}/favicon.ico"
 | 
			
		||||
            />
 | 
			
		||||
            <h:outputStylesheet name="css/style.css" />
 | 
			
		||||
            </h:head>
 | 
			
		||||
            <h:body>
 | 
			
		||||
            <h1>
 | 
			
		||||
                <ui:insert name="title">Gourmet Recipe Manager (web version)</ui:insert>
 | 
			
		||||
            </h1>
 | 
			
		||||
| 
						 | 
				
			
			@ -20,6 +24,6 @@
 | 
			
		|||
                    href="http://www.apache.org/licenses/LICENSE-2.0"
 | 
			
		||||
                >Apache License, Version 2.0</a>.
 | 
			
		||||
              <p>Based on Gourmet Recipe Manager by T. Hinkle</p>
 | 
			
		||||
            
 | 
			
		||||
            </h:body>
 | 
			
		||||
    </f:view>
 | 
			
		||||
</ui:composition>
 | 
			
		||||
							
								
								
									
										4
									
								
								src/main/resources/META-INF/resources/css/style.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/main/resources/META-INF/resources/css/style.css
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,4 @@
 | 
			
		|||
.redBox {
 | 
			
		||||
	color: red;
 | 
			
		||||
	background-color: blue;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -13,16 +13,19 @@
 | 
			
		|||
        <h:form id="form1">
 | 
			
		||||
            <p:panelGrid label="Find a Recipe" columns="1">
 | 
			
		||||
                <p:panelGrid columns="3">
 | 
			
		||||
                    <p:inputText id="searchFor" focus="true"
 | 
			
		||||
                    <p:focus for="@next"/>
 | 
			
		||||
                    <p:inputText id="searchFor"
 | 
			
		||||
                        placeholder="Recipe title (todo) cuisine, etc."
 | 
			
		||||
                        styleClass="redBox"
 | 
			
		||||
                        accesskey="f"
 | 
			
		||||
                        value="#{adminMainBean.searchText}"
 | 
			
		||||
                    />
 | 
			
		||||
                    <p:button id="find" label="Find"
 | 
			
		||||
                    <p:button id="find" value="Find"
 | 
			
		||||
                        defaultCommand="true"
 | 
			
		||||
                        action="#{adminMainBean.doFind}"
 | 
			
		||||
                    >
 | 
			
		||||
                    </p:button>
 | 
			
		||||
                    <p:button label="New Recipe"
 | 
			
		||||
                    <p:button value="New Recipe"
 | 
			
		||||
                        action="#{adminMainBean.doNewRecipe}"
 | 
			
		||||
                    >
 | 
			
		||||
                    </p:button>
 | 
			
		||||
| 
						 | 
				
			
			@ -33,13 +36,13 @@
 | 
			
		|||
            value="#{adminMainBean.searchResults}" var="row"
 | 
			
		||||
        >
 | 
			
		||||
            <p:column headerText="Icon">
 | 
			
		||||
                <image width="64" height="64"
 | 
			
		||||
                    value="/gourmetj/img/thumb/#{row.id}"
 | 
			
		||||
                <img height="40"
 | 
			
		||||
                    src="/img/thumb/#{row.id}"
 | 
			
		||||
                />
 | 
			
		||||
            </p:column>
 | 
			
		||||
            <p:column headerText="Recipe">
 | 
			
		||||
            <p:column headerText="Recipe" style="width: 600px">
 | 
			
		||||
                <h:commandLink action="#{adminMainBean.showRecipe}"
 | 
			
		||||
                    label="#{row.title}"
 | 
			
		||||
                    value="#{row.title}"
 | 
			
		||||
                />
 | 
			
		||||
            </p:column>
 | 
			
		||||
            <p:column headerText="Category">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user