Thumbnails!
This commit is contained in:
parent
3083b2596c
commit
11f77cf995
12
pom.xml
12
pom.xml
|
@ -56,7 +56,17 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.validation</groupId>
|
<groupId>javax.validation</groupId>
|
||||||
<artifactId>validation-api</artifactId>
|
<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>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -23,6 +23,8 @@ import com.mousetech.gourmetj.persistence.model.Recipe;
|
||||||
public interface RecipeRepository
|
public interface RecipeRepository
|
||||||
extends JpaRepository<Recipe, Long> {
|
extends JpaRepository<Recipe, Long> {
|
||||||
|
|
||||||
|
List<Recipe> findByTitleContaining(String searchText);
|
||||||
|
|
||||||
// final static String SQL_FIND_CATEGORIES =
|
// final static String SQL_FIND_CATEGORIES =
|
||||||
// "SELECT DISTINCT category from categories"
|
// "SELECT DISTINCT category from categories"
|
||||||
// + " where category is not null and category <> ''"
|
// + " where category is not null and category <> ''"
|
||||||
|
|
|
@ -2,6 +2,8 @@ package com.mousetech.gourmetj.persistence.service;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import javax.enterprise.context.ApplicationScoped;
|
import javax.enterprise.context.ApplicationScoped;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
@ -33,8 +35,11 @@ public class RecipeService implements Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Recipe> findByTitle(String searchText) {
|
public List<Recipe> findByTitle(String searchText) {
|
||||||
// TODO Auto-generated method stub
|
return recipeRepository.findByTitleContaining(searchText);
|
||||||
return null;
|
}
|
||||||
|
|
||||||
|
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"
|
xmlns:tc="http://myfaces.apache.org/tobago/component"
|
||||||
>
|
>
|
||||||
<f:view>
|
<f:view>
|
||||||
|
<h:head>
|
||||||
<title><ui:insert name="title">Gourmet Recipe Manager (web version)</ui:insert></title>
|
<title><ui:insert name="title">Gourmet Recipe Manager (web version)</ui:insert></title>
|
||||||
<link rel="icon" type="image/vnd.microsoft.icon"
|
<link rel="icon" type="image/vnd.microsoft.icon"
|
||||||
href="#{pageContext.contextPath}/favicon.ico"
|
href="#{pageContext.contextPath}/favicon.ico"
|
||||||
/>
|
/>
|
||||||
|
<h:outputStylesheet name="css/style.css" />
|
||||||
|
</h:head>
|
||||||
|
<h:body>
|
||||||
<h1>
|
<h1>
|
||||||
<ui:insert name="title">Gourmet Recipe Manager (web version)</ui:insert>
|
<ui:insert name="title">Gourmet Recipe Manager (web version)</ui:insert>
|
||||||
</h1>
|
</h1>
|
||||||
|
@ -20,6 +24,6 @@
|
||||||
href="http://www.apache.org/licenses/LICENSE-2.0"
|
href="http://www.apache.org/licenses/LICENSE-2.0"
|
||||||
>Apache License, Version 2.0</a>.
|
>Apache License, Version 2.0</a>.
|
||||||
<p>Based on Gourmet Recipe Manager by T. Hinkle</p>
|
<p>Based on Gourmet Recipe Manager by T. Hinkle</p>
|
||||||
|
</h:body>
|
||||||
</f:view>
|
</f:view>
|
||||||
</ui:composition>
|
</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">
|
<h:form id="form1">
|
||||||
<p:panelGrid label="Find a Recipe" columns="1">
|
<p:panelGrid label="Find a Recipe" columns="1">
|
||||||
<p:panelGrid columns="3">
|
<p:panelGrid columns="3">
|
||||||
<p:inputText id="searchFor" focus="true"
|
<p:focus for="@next"/>
|
||||||
|
<p:inputText id="searchFor"
|
||||||
placeholder="Recipe title (todo) cuisine, etc."
|
placeholder="Recipe title (todo) cuisine, etc."
|
||||||
|
styleClass="redBox"
|
||||||
|
accesskey="f"
|
||||||
value="#{adminMainBean.searchText}"
|
value="#{adminMainBean.searchText}"
|
||||||
/>
|
/>
|
||||||
<p:button id="find" label="Find"
|
<p:button id="find" value="Find"
|
||||||
defaultCommand="true"
|
defaultCommand="true"
|
||||||
action="#{adminMainBean.doFind}"
|
action="#{adminMainBean.doFind}"
|
||||||
>
|
>
|
||||||
</p:button>
|
</p:button>
|
||||||
<p:button label="New Recipe"
|
<p:button value="New Recipe"
|
||||||
action="#{adminMainBean.doNewRecipe}"
|
action="#{adminMainBean.doNewRecipe}"
|
||||||
>
|
>
|
||||||
</p:button>
|
</p:button>
|
||||||
|
@ -33,13 +36,13 @@
|
||||||
value="#{adminMainBean.searchResults}" var="row"
|
value="#{adminMainBean.searchResults}" var="row"
|
||||||
>
|
>
|
||||||
<p:column headerText="Icon">
|
<p:column headerText="Icon">
|
||||||
<image width="64" height="64"
|
<img height="40"
|
||||||
value="/gourmetj/img/thumb/#{row.id}"
|
src="/img/thumb/#{row.id}"
|
||||||
/>
|
/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Recipe">
|
<p:column headerText="Recipe" style="width: 600px">
|
||||||
<h:commandLink action="#{adminMainBean.showRecipe}"
|
<h:commandLink action="#{adminMainBean.showRecipe}"
|
||||||
label="#{row.title}"
|
value="#{row.title}"
|
||||||
/>
|
/>
|
||||||
</p:column>
|
</p:column>
|
||||||
<p:column headerText="Category">
|
<p:column headerText="Category">
|
||||||
|
|
Loading…
Reference in New Issue
Block a user