diff --git a/ccm-bundle-devel-wildfly-web/src/main/resources/log4j2.xml b/ccm-bundle-devel-wildfly-web/src/main/resources/log4j2.xml
index 3c20db8e4..9c55f1436 100644
--- a/ccm-bundle-devel-wildfly-web/src/main/resources/log4j2.xml
+++ b/ccm-bundle-devel-wildfly-web/src/main/resources/log4j2.xml
@@ -89,8 +89,11 @@
+
+
+ level="debug">
\ No newline at end of file
diff --git a/ccm-cms/src/main/java/org/librecms/contentsection/rs/Images.java b/ccm-cms/src/main/java/org/librecms/contentsection/rs/Images.java
index 9cf49d7ce..83ac205b5 100644
--- a/ccm-cms/src/main/java/org/librecms/contentsection/rs/Images.java
+++ b/ccm-cms/src/main/java/org/librecms/contentsection/rs/Images.java
@@ -18,19 +18,32 @@
*/
package org.librecms.contentsection.rs;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.librecms.assets.Image;
import org.librecms.contentsection.Asset;
import org.librecms.contentsection.AssetRepository;
import org.librecms.contentsection.ContentSection;
import org.librecms.contentsection.ContentSectionRepository;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Iterator;
import java.util.Optional;
import javax.enterprise.context.RequestScoped;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.stream.ImageInputStream;
import javax.inject.Inject;
+import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
/**
@@ -41,17 +54,55 @@ import javax.ws.rs.core.Response;
@Path("/{content-section}/images/")
public class Images {
+ private static final Logger LOGGER = LogManager.getLogger(Images.class);
+
@Inject
private ContentSectionRepository sectionRepo;
@Inject
private AssetRepository assetRepo;
+ /**
+ * Return the image requested by the provided content section and path.
+ *
+ * The URL for an image contains the content section and the path to the
+ * image. If there is no image for the provided content section and path an
+ * 404 error is returned.
+ *
+ * This method also accepts two parameters which can be specified as query
+ * parameters on the URL: {@code width} and {@code height}. If one or both
+ * are provided the image is scaled before it is send the the user agent
+ * which requested the image. The method preserves the aspect ratio of the
+ * image. If the parameters have different scale factors meaning that the
+ * aspect ratio of the image would not be preserved the parameter with the
+ * smallest difference to one if used. The other parameter is ignored and
+ * replaced with a value which preserves the aspect ratio.
+ *
+ * @param sectionName The name of the content section which contains the
+ * image.
+ * @param path The path to the image.
+ * @param widthParam The width to scale the image. If the value is 0 or
+ * less or the value is not a valid integer it parameter
+ * is ignored.
+ * @param heightParam The height to scale the image. If the value is 0 or
+ * less or the value is not a valid integer it parameter
+ * is ignored.
+ *
+ * @return A {@link Response} containing the scaled image or an error value.
+ */
@GET
@Path("/{path:.+}")
public Response getImage(
- @PathParam("content-section") final String sectionName,
- @PathParam("path") final String path) {
+ @PathParam("content-section")
+ final String sectionName,
+ @PathParam("path")
+ final String path,
+ @QueryParam("width")
+ @DefaultValue("-1")
+ final String widthParam,
+ @QueryParam("height")
+ @DefaultValue("-1")
+ final String heightParam) {
final Optional section = sectionRepo
.findByLabel(sectionName);
@@ -63,20 +114,84 @@ public class Images {
.build();
}
- final Optional asset = assetRepo.findByPath(section.get(),
+ final Optional asset = assetRepo.findByPath(section.get(),
path);
if (asset.isPresent()) {
if (asset.get() instanceof Image) {
final Image image = (Image) asset.get();
final byte[] data = image.getData();
+ final String mimeType = image.getMimeType().toString();
+ final InputStream inputStream = new ByteArrayInputStream(data);
+ final BufferedImage bufferedImage;
+ final String imageFormat;
+ try {
+ final ImageInputStream imageInputStream = ImageIO
+ .createImageInputStream(inputStream);
+ final Iterator readers = ImageIO
+ .getImageReaders(imageInputStream);
+ final ImageReader imageReader;
+ if (readers.hasNext()) {
+ imageReader = readers.next();
+ } else {
+ LOGGER.error("No image reader for image {} (UUID: {}) "
+ + "available.",
+ image.getDisplayName(),
+ image.getUuid());
+ return Response.serverError().build();
+ }
+ imageReader.setInput(imageInputStream);
+ bufferedImage = imageReader.read(0);
+ imageFormat = imageReader.getFormatName();
+ } catch (IOException ex) {
+ LOGGER.error("Failed to load image {} (UUID: {}).",
+ image.getDisplayName(),
+ image.getUuid());
+ LOGGER.error(ex);
+ return Response.serverError().build();
+ }
+
+ // Yes, this is correct. The parameters provided in the URL
+ // are expected to be integers. The private scaleImage method
+ // works with floats to be accurate (divisions are performed
+ // with the values for width and height)
+ final int width = parseScaleParameter(widthParam, "width");
+ final int height = parseScaleParameter(heightParam, "height");
+ final java.awt.Image scaledImage = scaleImage(bufferedImage,
+ width,
+ height);
+
+ final ByteArrayOutputStream outputStream
+ = new ByteArrayOutputStream();
+ final BufferedImage bufferedScaledImage = new BufferedImage(
+ scaledImage.getWidth(null),
+ scaledImage.getHeight(null),
+ bufferedImage.getType());
+ bufferedScaledImage
+ .getGraphics()
+ .drawImage(scaledImage, 0, 0, null);
+ try {
+ ImageIO
+ .write(bufferedScaledImage, imageFormat, outputStream);
+ } catch (IOException ex) {
+ LOGGER.error("Failed to render scaled variant of image {} "
+ + "(UUID: {}).",
+ image.getDisplayName(),
+ image.getUuid());
+ LOGGER.error(ex);
+ return Response.serverError().build();
+ }
+
+// return Response
+ // .ok(String.format(
+ // "Requested image \"%s\" in content section \"%s\"",
+ // path,
+ // section.get().getLabel()),
+ // "text/plain")
+ // .build();
return Response
- .ok(String.format(
- "Requested image \"%s\" in content section \"%s\"",
- path,
- section.get().getLabel()),
- "text/plain")
+ .ok(outputStream.toByteArray(), mimeType)
.build();
} else {
return Response
@@ -107,4 +222,180 @@ public class Images {
// return builder.build();
}
+ /**
+ * Provides several properties of an image to a user agent as JSON.
+ *
+ * @param sectionName The name of the content section which contains the
+ * image.
+ * @param path The path to the image.
+ *
+ * @return A {@link Response} with the informations about the requested
+ * image.
+ */
+ @GET
+ @Path("/{path:.*}/properties")
+ public Response getImageProperties(
+ @PathParam("content-section") final String sectionName,
+ @PathParam("path") final String path) {
+
+ final Optional section = sectionRepo
+ .findByLabel(sectionName);
+ if (!section.isPresent()) {
+ return Response
+ .status(Response.Status.NOT_FOUND)
+ .entity(String.format("No content section \"%s\" available.",
+ sectionName))
+ .build();
+ }
+
+ final Optional asset = assetRepo.findByPath(section.get(),
+ path);
+
+ if (asset.isPresent()) {
+ if (asset.get() instanceof Image) {
+ final Image image = (Image) asset.get();
+ final byte[] data = image.getData();
+ final String mimeType = image.getMimeType().toString();
+
+ final InputStream inputStream = new ByteArrayInputStream(data);
+ final BufferedImage bufferedImage;
+ try {
+ bufferedImage = ImageIO.read(inputStream);
+ } catch (IOException ex) {
+ LOGGER.error("Failed to load image {} (UUID: {}).",
+ image.getDisplayName(),
+ image.getUuid());
+ LOGGER.error(ex);
+ return Response.serverError().build();
+ }
+
+ final String imageProperties = String
+ .format("{%n"
+ + " \"name\": \"%s\",%n"
+ + " \"filename\": \"%s\",%n"
+ + " \"mimetype\": \"%s\",%n"
+ + " \"width\": %d,%n"
+ + " \"height\": %d%n"
+ + "}",
+ image.getDisplayName(),
+ image.getFileName(),
+ mimeType,
+ bufferedImage.getWidth(),
+ bufferedImage.getHeight());
+
+ return Response
+ .ok(imageProperties, "application/json")
+ .build();
+ } else {
+ return Response
+ .status(Response.Status.NOT_FOUND)
+ .entity(String
+ .format("The asset found at the requested path \"%s\" "
+ + "is not an image.",
+ path))
+ .build();
+ }
+ } else {
+ return Response
+ .status(Response.Status.NOT_FOUND)
+ .entity(String
+ .format("The requested image \"%s\" does not exist.",
+ path))
+ .build();
+ }
+ }
+
+ /**
+ * Helper method for parsing the parameters for scaling an image into
+ * integers.
+ *
+ * @param parameterValue The value to parse as integer.
+ * @param parameter The name of the parameter (used for logging
+ * output).
+ *
+ * @return The integer value of the parameter or -1 if the provided value is
+ * not a valid integer.
+ */
+ private int parseScaleParameter(final String parameterValue,
+ final String parameter) {
+ try {
+ return Integer.parseInt(parameterValue);
+ } catch (NumberFormatException ex) {
+ LOGGER.warn("Provided value \"{}\" for parameter \"{}\" is "
+ + "not an integer. Ignoring value.",
+ parameterValue,
+ parameter);
+ LOGGER.warn(ex);
+ return -1;
+ }
+ }
+
+ /**
+ * Helper method for scaling the image while preserving the aspect ratio of
+ * the image.
+ *
+ * @param image The image to scale.
+ * @param scaleToWidth The width to which is scaled.
+ * @param scaleToHeight The height the which image is scaled.
+ *
+ * @return The scaled image.
+ */
+ private java.awt.Image scaleImage(final BufferedImage image,
+ final float scaleToWidth,
+ final float scaleToHeight) {
+
+ final float originalWidth = image.getWidth();
+ final float originalHeight = image.getHeight();
+ final float originalAspectRatio = originalWidth / originalHeight;
+
+ if (scaleToWidth > 0 && scaleToHeight > 0) {
+ //Check if parameters preserve aspectRatio. If not use the smaller
+ //scale factor.
+
+ final float scaleToAspectRatio = scaleToWidth / scaleToHeight;
+ if (Math.abs(scaleToAspectRatio - originalAspectRatio) < 0.009f) {
+ // Scale the image.
+
+ return image.getScaledInstance(Math.round(scaleToWidth),
+ Math.round(scaleToHeight),
+ java.awt.Image.SCALE_SMOOTH);
+ } else {
+ //Use the scale factor nearer to one for both dimensions
+ final float scaleFactorWidth = scaleToWidth / originalWidth;
+ final float scaleFactorHeight = scaleToHeight / originalHeight;
+ final float differenceWidth = Math.abs(scaleFactorWidth - 1);
+ final float differenceHeight = Math.abs(scaleFactorHeight - 1);
+
+ final float scaleFactor;
+ if (differenceWidth < differenceHeight) {
+ scaleFactor = scaleFactorWidth;
+ } else {
+ scaleFactor = scaleFactorHeight;
+ }
+
+ return scaleImage(image,
+ originalWidth * scaleFactor,
+ originalHeight * scaleFactor);
+ }
+
+ } else if (scaleToWidth > 0 && scaleToHeight <= 0) {
+ //Calculate the height to which to image is scaled based on the
+ //scale factor for the width
+ final float scaleFactor = scaleToWidth / originalWidth;
+ final float height = originalHeight * scaleFactor;
+
+ return scaleImage(image, scaleToWidth, height);
+ } else if (scaleToWidth <= 0 && scaleToHeight >= 0) {
+ //Calculate the width to which to image is scaled based on the
+ //scale factor for the height
+ final float scaleFactor = scaleToHeight / originalHeight;
+ final float width = originalWidth * scaleFactor;
+
+ return scaleImage(image, width, scaleToHeight);
+ } else {
+ //Return the image as is.
+ return image;
+ }
+ }
+
}