Thumbnails!

This commit is contained in:
Tim Holloway 2021-12-28 21:12:23 -05:00
parent 3083b2596c
commit 11f77cf995
8 changed files with 331 additions and 11 deletions

12
pom.xml
View File

@ -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>

View File

@ -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 <> ''"

View File

@ -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);
}
// /**

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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>

View File

@ -0,0 +1,4 @@
.redBox {
color: red;
background-color: blue;
}

View File

@ -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">