Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resize images #1209

Merged
merged 1 commit into from
Apr 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@
@Log4j2
public class FileUploadApiController {

private static final int IMAGE_THUMB_MAX_WIDTH_PX = 500;
private static final int IMAGE_THUMB_MAX_HEIGHT_PX = 500;

private final FileUploadManager fileUploadManager;

@Autowired
Expand All @@ -50,36 +47,12 @@ public FileUploadApiController(FileUploadManager fileUploadManager) {
}

@PostMapping("/file/upload")
public ResponseEntity<String> uploadFile(@RequestParam(required = false, value = "resizeImage", defaultValue = "false") Boolean resizeImage,
@RequestBody UploadBase64FileModification upload) {

public ResponseEntity<String> uploadFile(@RequestBody UploadBase64FileModification upload) {
try {
final var mimeType = MimeTypeUtils.parseMimeType(upload.getType());
if (Boolean.TRUE.equals(resizeImage)) {
upload = resize(upload, mimeType);
}
return ResponseEntity.ok(fileUploadManager.insertFile(upload));
} catch (Exception e) {
log.error("error while uploading image", e);
return ResponseEntity.badRequest().build();
}
}

private UploadBase64FileModification resize(UploadBase64FileModification upload, MimeType mimeType) throws IOException {
BufferedImage image = ImageIO.read(new ByteArrayInputStream(upload.getFile()));
//resize only if the image is bigger than 500px on one of the side
if (image.getWidth() > IMAGE_THUMB_MAX_WIDTH_PX || image.getHeight() > IMAGE_THUMB_MAX_HEIGHT_PX) {
UploadBase64FileModification resized = new UploadBase64FileModification();
BufferedImage thumbImg = Scalr.resize(image, Scalr.Method.QUALITY, Scalr.Mode.AUTOMATIC, IMAGE_THUMB_MAX_WIDTH_PX, IMAGE_THUMB_MAX_HEIGHT_PX, Scalr.OP_ANTIALIAS);
try (final var baos = new ByteArrayOutputStream()) {
ImageIO.write(thumbImg, mimeType.getSubtype(), baos);
resized.setFile(baos.toByteArray());
}
resized.setAttributes(upload.getAttributes());
resized.setName(upload.getName());
resized.setType(upload.getType());
return resized;
}
return upload;
}
}
2 changes: 1 addition & 1 deletion src/main/java/alfio/manager/FileDownloadManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public DownloadedFile downloadFile(String url) {
if(callSuccessful(response)) {
String[] parts = Pattern.compile("/").split(url);
String name = parts[parts.length - 1];
if(Objects.nonNull(response.body()) && response.body().length <= FileUploadManager.MAXIMUM_ALLOWED_SIZE) {
if(Objects.nonNull(response.body())) {
return new DownloadedFile(
response.body(),
name,
Expand Down
68 changes: 57 additions & 11 deletions src/main/java/alfio/manager/FileUploadManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,22 @@
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.github.benmanes.caffeine.cache.RemovalListener;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.imgscalr.Scalr;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.Files;
import java.time.Duration;
import java.util.*;

Expand All @@ -42,22 +47,33 @@
@RequiredArgsConstructor
public class FileUploadManager {

static final int IMAGE_THUMB_MAX_WIDTH_PX = 300;
static final int IMAGE_THUMB_MAX_HEIGHT_PX = 200;
/**
* Maximum allowed file size is 200kb
*/
static final int MAXIMUM_ALLOWED_SIZE = 1024 * 200;
private static final int MAXIMUM_ALLOWED_SIZE = 1024 * 200;
private static final MimeType IMAGE_TYPE = MimeType.valueOf("image/*");
private final FileUploadRepository repository;
private final Cache<String, File> cache = Caffeine.newBuilder()
.maximumSize(20)
.expireAfterWrite(Duration.ofMinutes(20))
.removalListener((String key, File value, RemovalCause cause) -> {
if(value != null) {
boolean result = value.delete();
log.trace("deleted {}: {}", key, result);
}
})
.removalListener(removalListener())
.build();

private static RemovalListener<String, File> removalListener() {
return (String key, File value, RemovalCause cause) -> {
if (value != null) {
try {
Files.delete(value.toPath());
log.trace("deleted {}", key);
} catch(Exception ex) {
log.trace("Error while deleting file", ex);
}
}
};
}

public Optional<FileBlobMetadata> findMetadata(String id) {
return repository.findById(id);
}
Expand All @@ -79,12 +95,13 @@ public void outputFile(String id, OutputStream out) {
}
}


public String insertFile(UploadBase64FileModification file) {
Validate.exclusiveBetween(1, MAXIMUM_ALLOWED_SIZE, file.getFile().length);
String digest = DigestUtils.sha256Hex(file.getFile());
final var mimeType = MimeTypeUtils.parseMimeType(file.getType());
var upload = resizeIfNeeded(file, mimeType);
Validate.exclusiveBetween(1, MAXIMUM_ALLOWED_SIZE, upload.getFile().length);
String digest = DigestUtils.sha256Hex(upload.getFile());
if (Integer.valueOf(0).equals(repository.isPresent(digest))) {
repository.upload(file, digest, getAttributes(file));
repository.upload(upload, digest, getAttributes(upload));
}
return digest;
}
Expand All @@ -94,6 +111,35 @@ public void cleanupUnreferencedBlobFiles(Date date) {
log.debug("removed {} unused file_blob", deleted);
}

/**
* @author <a href="https://github.com/emassip">Etienne M.</a>
*/
private UploadBase64FileModification resizeIfNeeded(UploadBase64FileModification upload, MimeType mimeType) {
if (!mimeType.isCompatibleWith(IMAGE_TYPE)) {
// not an image, nothing to do here.
return upload;
}
try {
BufferedImage image = ImageIO.read(new ByteArrayInputStream(upload.getFile()));
// resize only if the image is bigger than target size on either side
if (image.getWidth() > IMAGE_THUMB_MAX_WIDTH_PX || image.getHeight() > IMAGE_THUMB_MAX_HEIGHT_PX) {
UploadBase64FileModification resized = new UploadBase64FileModification();
BufferedImage thumbImg = Scalr.resize(image, Scalr.Method.QUALITY, Scalr.Mode.AUTOMATIC, IMAGE_THUMB_MAX_WIDTH_PX, IMAGE_THUMB_MAX_HEIGHT_PX, Scalr.OP_ANTIALIAS);
try (final var baos = new ByteArrayOutputStream()) {
ImageIO.write(thumbImg, mimeType.getSubtype(), baos);
resized.setFile(baos.toByteArray());
}
resized.setAttributes(upload.getAttributes());
resized.setName(upload.getName());
resized.setType(upload.getType());
return resized;
}
return upload;
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
}

private Map<String, String> getAttributes(UploadBase64FileModification file) {
if(!StringUtils.startsWith(file.getType(), "image/")) {
return Collections.emptyMap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ <h3>Logo</h3>
<div class="form-group">
<label for="imageFile">Image</label>
<div id="imageFile" class="drop-file-zone wMarginBottom well" data-accept="image/*" data-ngf-pattern="'image/*'" data-ng-model="droppedFile" data-ngf-drop data-ngf-select data-ngf-multiple="false" data-ngf-allow-dir="false" data-ngf-drag-over-class="'drop-file-zone-hover'">
Drop image here or click to upload (Maximum size : 200KB)
Drop image here or click to upload (Maximum size : 1MB)
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ <h3>Image</h3>
data-accept="image/*" data-ngf-pattern="'image/*'"
data-ng-model="$ctrl.droppedFile"
data-ngf-drop data-ngf-select data-ngf-multiple="false" data-ngf-allow-dir="false" data-ngf-drag-over-class="'drop-file-zone-hover'">
Drop image here or click to upload (Maximum size : 200KB)
Drop image here or click to upload (Maximum size : 1MB)
</div>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/main/webapp/resources/js/admin/service/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -997,8 +997,8 @@
deferred.reject('Your image was not uploaded correctly.Please upload the image again');
} else if (!((files[0].type === 'image/png') || (files[0].type === 'image/jpeg') || (files[0].type === 'image/gif') || (files[0].type === 'image/svg+xml'))) {
deferred.reject('Only PNG, JPG, GIF or SVG image files are accepted');
} else if (files[0].size > (1024 * 200)) {
deferred.reject('Image size exceeds the allowable limit 200KB');
} else if (files[0].size > (1024 * 1024)) {
deferred.reject('Image is too big');
} else {
reader.readAsDataURL(files[0]);
}
Expand Down
33 changes: 28 additions & 5 deletions src/test/java/alfio/manager/FileUploadManagerIntegrationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@
import org.apache.commons.lang3.time.DateUtils;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Date;
import java.util.Objects;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;
Expand All @@ -50,7 +51,7 @@ class FileUploadManagerIntegrationTest extends BaseIntegrationTest {
private static final byte[] FILE = {1,2,3,4};

@Test
public void testInsert() {
void testInsert() {
UploadBase64FileModification toInsert = new UploadBase64FileModification();
toInsert.setFile(FILE);
toInsert.setName("myfile.txt");
Expand All @@ -76,7 +77,7 @@ public void testInsert() {


@Test
public void testInsertImage() {
void testInsertImage() {
UploadBase64FileModification toInsert = new UploadBase64FileModification();
toInsert.setFile(ONE_PIXEL_BLACK_GIF);
toInsert.setName("image.gif");
Expand All @@ -95,7 +96,29 @@ public void testInsertImage() {
}

@Test
public void testFindMetadataNotPresent() {
void testFindMetadataNotPresent() {
assertFalse(fileUploadManager.findMetadata("unknownid").isPresent());
}

@Test
void testInsertResizedImage() throws IOException {
// Image credit: NASA, ESA, CSA, and STScI
try (var in = getClass().getResourceAsStream("/images/main_image_star-forming_region_carina_reduced.jpg")) {
UploadBase64FileModification toInsert = new UploadBase64FileModification();
toInsert.setFile(Objects.requireNonNull(in).readAllBytes());
toInsert.setName("image.jpg");
toInsert.setType("image/jpeg");
String id = fileUploadManager.insertFile(toInsert);

Optional<FileBlobMetadata> metadata = fileUploadManager.findMetadata(id);

assertTrue(metadata.isPresent());

assertEquals(String.valueOf(FileUploadManager.IMAGE_THUMB_MAX_WIDTH_PX), metadata.get().getAttributes().get("width"));
assertEquals("174", metadata.get().getAttributes().get("height"));

fileUploadManager.cleanupUnreferencedBlobFiles(DateUtils.addDays(new Date(), 1));
assertFalse(fileUploadManager.findMetadata(id).isPresent());
}
}
}
1 change: 1 addition & 0 deletions src/test/resources/images/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Image credit: NASA, ESA, CSA, and STScI
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.