diff --git a/build.gradle b/build.gradle index 81dbaae1a5..a99a7a30f6 100644 --- a/build.gradle +++ b/build.gradle @@ -138,6 +138,7 @@ dependencies { compile "com.openhtmltopdf:openhtmltopdf-core:1.0.2" compile "com.openhtmltopdf:openhtmltopdf-pdfbox:1.0.2" compile "ch.digitalfondue.jfiveparse:jfiveparse:0.7.1" + compile "guru.nidi.com.kitfox:svgSalamander:1.1.3" /**/ compile "com.google.zxing:core:3.4.0" compile "com.google.zxing:javase:3.4.0" diff --git a/src/main/java/alfio/controller/api/admin/FileUploadApiController.java b/src/main/java/alfio/controller/api/admin/FileUploadApiController.java index bd86752a25..7afa468060 100644 --- a/src/main/java/alfio/controller/api/admin/FileUploadApiController.java +++ b/src/main/java/alfio/controller/api/admin/FileUploadApiController.java @@ -19,22 +19,36 @@ import alfio.manager.FileUploadManager; import alfio.model.modification.UploadBase64FileModification; import lombok.extern.log4j.Log4j2; -import org.apache.commons.lang3.StringUtils; + import org.imgscalr.Scalr; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; +import org.springframework.util.MimeType; +import org.springframework.util.MimeTypeUtils; import org.springframework.web.bind.annotation.*; +import com.kitfox.svg.SVGUniverse; +import com.kitfox.svg.app.beans.SVGIcon; + import javax.imageio.ImageIO; + +import java.awt.Dimension; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URI; @RestController @RequestMapping("/admin/api") @Log4j2 public class FileUploadApiController { + private static final MimeType MIME_TYPE_IMAGE_SVG = MimeTypeUtils.parseMimeType("image/svg+xml"); + + 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 @@ -45,30 +59,69 @@ public FileUploadApiController(FileUploadManager fileUploadManager) { @PostMapping("/file/upload") public ResponseEntity uploadFile(@RequestParam(required = false, value = "resizeImage", defaultValue = "false") Boolean resizeImage, @RequestBody UploadBase64FileModification upload) { - try { - if (Boolean.TRUE.equals(resizeImage)) { - 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() > 500 || image.getHeight() > 500) { - UploadBase64FileModification resized = new UploadBase64FileModification(); - BufferedImage thumbImg = Scalr.resize(image, Scalr.Method.QUALITY, Scalr.Mode.AUTOMATIC, 500, 500, Scalr.OP_ANTIALIAS); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - ImageIO.write(thumbImg, StringUtils.substringAfter(upload.getType(), "/"), baos); - - resized.setFile(baos.toByteArray()); - resized.setAttributes(upload.getAttributes()); - resized.setName(upload.getName()); - resized.setType(upload.getType()); - upload = resized; - } + try { + final var mimeType = MimeTypeUtils.parseMimeType(upload.getType()); + if (MIME_TYPE_IMAGE_SVG.equalsTypeAndSubtype(mimeType)) { + upload = rasterizeSVG(upload, resizeImage); + } else 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; + } + + private UploadBase64FileModification rasterizeSVG(UploadBase64FileModification upload, Boolean resizeImage) throws IOException { + + final SVGUniverse uni = new SVGUniverse(); + final URI uri = uni.loadSVG(upload.getInputStream(), "_"); + // use apply 10% margin to prevent final image size to exceed max allowed size + final int maxWidthWithMargin = (int) (IMAGE_THUMB_MAX_WIDTH_PX * 0.9); + final int maxHeightWithMargin = (int) (IMAGE_THUMB_MAX_HEIGHT_PX * 0.9); + + final SVGIcon icon = new SVGIcon(); + icon.setSvgUniverse(uni); + icon.setSvgURI(uri); + icon.setAntiAlias(true); + if(icon.getIconWidth() > maxHeightWithMargin || icon.getIconHeight() > maxHeightWithMargin) { + icon.setAutosize(SVGIcon.AUTOSIZE_STRETCH); + icon.setPreferredSize(new Dimension(Math.min(maxWidthWithMargin, icon.getIconWidth()), Math.min(maxHeightWithMargin, icon.getIconHeight()))); + } + final BufferedImage bi = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB); + icon.paintIcon(null, bi.createGraphics(), 0, 0); + + final UploadBase64FileModification rasterized = new UploadBase64FileModification(); + try (final var baos = new ByteArrayOutputStream()) { + ImageIO.write(bi, "PNG", baos); + rasterized.setFile(baos.toByteArray()); + } + rasterized.setAttributes(upload.getAttributes()); + rasterized.setName(upload.getName() + ".png"); + rasterized.setType(MimeTypeUtils.IMAGE_PNG_VALUE); + + uni.removeDocument(uri); + + return rasterized; + } } diff --git a/src/main/webapp/resources/angular-templates/admin/partials/event/fragment/edit-event-header.html b/src/main/webapp/resources/angular-templates/admin/partials/event/fragment/edit-event-header.html index 2f6958e1b3..72e113fe84 100644 --- a/src/main/webapp/resources/angular-templates/admin/partials/event/fragment/edit-event-header.html +++ b/src/main/webapp/resources/angular-templates/admin/partials/event/fragment/edit-event-header.html @@ -123,7 +123,7 @@

URLs Configuration

diff --git a/src/main/webapp/resources/js/admin/directive/admin-directive.js b/src/main/webapp/resources/js/admin/directive/admin-directive.js index 73933ea5e6..3379ac27c4 100644 --- a/src/main/webapp/resources/js/admin/directive/admin-directive.js +++ b/src/main/webapp/resources/js/admin/directive/admin-directive.js @@ -534,8 +534,8 @@ }; if (files.length <= 0) { alert('Your image not uploaded correctly.Please upload the image again'); - } else if (!((files[0].type === 'image/png') || (files[0].type === 'image/jpeg'))) { - alert('only png or jpeg files are accepted'); + } else if (!((files[0].type === 'image/png') || (files[0].type === 'image/jpeg') || (files[0].type === 'image/gif') || (files[0].type === 'image/svg+xml'))) { + alert('Only PNG, JPG, GIF or SVG image files are accepted'); } else if (files[0].size > (1024 * 200)) { alert('Image size exceeds the allowable limit 200KB'); } else { diff --git a/website/content/en/docs/Event Setup/create-in-person-event.md b/website/content/en/docs/Event Setup/create-in-person-event.md index 9516aff1a0..553506b992 100644 --- a/website/content/en/docs/Event Setup/create-in-person-event.md +++ b/website/content/en/docs/Event Setup/create-in-person-event.md @@ -105,7 +105,8 @@ Alf.io currently supports the following formats: - PNG - JPG - +- GIF +- SVG ## Seats and payment info