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