Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanseifert committed Nov 17, 2022
2 parents 3e7407f + fc19646 commit 4636128
Show file tree
Hide file tree
Showing 36 changed files with 1,136 additions and 400 deletions.
12 changes: 12 additions & 0 deletions changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@
xsi:schemaLocation="http://maven.apache.org/changes/1.0.0 http://maven.apache.org/plugins/maven-changes-plugin/xsd/changes-1.0.0.xsd">
<body>

<release version="1.15.0" date="2022-11-17">
<action type="add" dev="sseifert">
Add Rendition.getUriTemplate method to build URI templates for getting scaled or auto-cropped renditions of an asset with the restrictions of the rendition e.g. aspect ratio.
</action>
<action type="update" dev="sseifert" issue="WHAN-53">
File Upload Granite UI Widget: Allow video/mpeg and video/quicktime by default and support updating thumbnails.
</action>
<action type="fix" dev="sseifert">
Dynamic Media Support: Fix smart-cropping rendition validation in case unconstrained media formats are used (without exact size, e.g. only minimum width) and the original image ratio excaclty matches the requested ratio.
</action>
</release>

<release version="1.14.18" date="2022-11-09">
<action type="update" dev="sseifert">
Dynamic Media Support: Make Smart Crop rendition validation configurable (default: enabled).
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

<groupId>io.wcm</groupId>
<artifactId>io.wcm.handler.media</artifactId>
<version>1.14.18</version>
<version>1.15.0</version>
<packaging>jar</packaging>

<name>Media Handler</name>
Expand All @@ -49,7 +49,7 @@
<site.url.module.prefix>handler/media</site.url.module.prefix>

<!-- Enable reproducible builds -->
<project.build.outputTimestamp>2022-11-09T09:42:26Z</project.build.outputTimestamp>
<project.build.outputTimestamp>2022-11-17T08:31:47Z</project.build.outputTimestamp>
</properties>

<dependencies>
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/io/wcm/handler/media/Asset.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public interface Asset extends Adaptable {
* asset and the max. width/height of it's original rendition.
* @param type URI template type
* @return URI template
* @throws UnsupportedOperationException if the original rendition is not an image or a vector image.
* @throws UnsupportedOperationException if the original rendition is not an image or it is a vector image.
*/
@NotNull
UriTemplate getUriTemplate(@NotNull UriTemplateType type);
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/io/wcm/handler/media/Rendition.java
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,18 @@ default double getRatio() {
* rendition is returned that fulfills all other media format restrictions, this flag is set to true.
*/
boolean isFallback();

/**
* Generate an URI template for the rendition.
* The URI template ignores the actual resolution of this rendition and allows to scale the rendition
* to any size within the maximum range of width/height, keeping the aspect ratio and respecting
* both the original image and probably configured cropping parameters.
* @param type URI template type. It is not supported to use {@link UriTemplateType#CROP_CENTER}.
* @return URI template
* @throws IllegalArgumentException if {@link UriTemplateType#CROP_CENTER} is used
* @throws UnsupportedOperationException if the original rendition is not an image or it is a vector image.
*/
@NotNull
UriTemplate getUriTemplate(@NotNull UriTemplateType type);

}
14 changes: 10 additions & 4 deletions src/main/java/io/wcm/handler/media/impl/ImageFileServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,18 @@ protected byte[] getBinaryData(Resource resource, SlingHttpServletRequest reques
}

// if only width or only height is given - derive other value from ratio
double layerRatio = Ratio.get(layer.getWidth(), layer.getHeight());
double originalRatio;
if (cropDimension != null) {
originalRatio = Ratio.get(cropDimension);
}
else {
originalRatio = Ratio.get(layer.getWidth(), layer.getHeight());
}
if (width == 0) {
width = (int)Math.round(height * layerRatio);
width = (int)Math.round(height * originalRatio);
}
else if (height == 0) {
height = (int)Math.round(width / layerRatio);
height = (int)Math.round(width / originalRatio);
}

// if required: crop image
Expand All @@ -131,7 +137,7 @@ else if (height == 0) {
else {
// if image ratio that is requested does not match with the given ratio apply a center-crop here
double requestedRatio = Ratio.get(width, height);
if (!Ratio.matches(layerRatio, requestedRatio)) {
if (!Ratio.matches(originalRatio, requestedRatio)) {
cropDimension = ImageTransformation.calculateAutoCropDimension(layer.getWidth(), layer.getHeight(), requestedRatio);
layer.crop(cropDimension.getRectangle());
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/io/wcm/handler/media/package-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
/**
* Media Handler API.
*/
@org.osgi.annotation.versioning.Version("1.17")
@org.osgi.annotation.versioning.Version("1.18")
package io.wcm.handler.media;
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
if (dimension == null) {
throw new IllegalArgumentException("Unable to get dimension for original rendition of asset: " + getPath());
}
return new DamUriTemplate(type, dimension, damContext, defaultMediaArgs);
return new DamUriTemplate(type, dimension, original, null, null, null, damContext);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ public Asset getAsset() {
return asset;
}

/**
* @return Media Args from media request
*/
public MediaArgs getMediaArgs() {
return mediaArgs;
}

/**
* @return Media handler config
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
import io.wcm.handler.media.MediaArgs;
import io.wcm.handler.media.MediaFileType;
import io.wcm.handler.media.Rendition;
import io.wcm.handler.media.UriTemplate;
import io.wcm.handler.media.UriTemplateType;
import io.wcm.handler.media.format.MediaFormat;
import io.wcm.handler.url.UrlHandler;
import io.wcm.sling.commons.adapter.AdaptTo;
Expand Down Expand Up @@ -291,6 +293,17 @@ public boolean isFallback() {
return fallback;
}

@Override
public @NotNull UriTemplate getUriTemplate(@NotNull UriTemplateType type) {
if (this.rendition == null) {
throw new IllegalStateException("Rendition is not valid.");
}
if (type == UriTemplateType.CROP_CENTER) {
throw new IllegalArgumentException("CROP_CENTER not supported for rendition URI templates.");
}
return this.rendition.getUriTemplate(type, damContext);
}

@Override
@SuppressWarnings("null")
public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
Expand Down
170 changes: 115 additions & 55 deletions src/main/java/io/wcm/handler/mediasource/dam/impl/DamUriTemplate.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,98 +25,158 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.Resource;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import com.day.cq.dam.api.Rendition;

import io.wcm.handler.media.CropDimension;
import io.wcm.handler.media.Dimension;
import io.wcm.handler.media.MediaArgs;
import io.wcm.handler.media.UriTemplate;
import io.wcm.handler.media.UriTemplateType;
import io.wcm.handler.media.impl.ImageFileServlet;
import io.wcm.handler.media.impl.MediaFileServlet;
import io.wcm.handler.mediasource.dam.impl.dynamicmedia.DynamicMediaPath;
import io.wcm.handler.mediasource.dam.impl.dynamicmedia.NamedDimension;
import io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop;
import io.wcm.handler.url.UrlHandler;
import io.wcm.sling.commons.adapter.AdaptTo;

/**
* Generates URI templates for asset renditions - with or without Dynamic Media.
*/
class DamUriTemplate implements UriTemplate {

private final String uriTemplate;
private final UriTemplateType type;
private final String uriTemplate;
private final Dimension dimension;

DamUriTemplate(@NotNull UriTemplateType type, @NotNull Dimension dimension,
@NotNull DamContext damContext, @NotNull MediaArgs mediaArgs) {
this.uriTemplate = buildUriTemplate(type, damContext, mediaArgs);
@NotNull Rendition rendition, @Nullable CropDimension cropDimension, @Nullable Integer rotation,
@Nullable Double ratio, @NotNull DamContext damContext) {
this.type = type;
this.dimension = dimension;
}

private static String buildUriTemplate(@NotNull UriTemplateType type, @NotNull DamContext damContext,
@NotNull MediaArgs mediaArgs) {
String url = null;
Dimension validatedDimension = null;
if (damContext.isDynamicMediaEnabled() && damContext.isDynamicMediaAsset()) {
// if DM is enabled: try to get rendition URL from dynamic media
String productionAssetUrl = damContext.getDynamicMediaServerUrl();
if (productionAssetUrl != null) {
switch (type) {
case CROP_CENTER:
url = productionAssetUrl + DynamicMediaPath.buildImage(damContext)
+ "?wid=" + URI_TEMPLATE_PLACEHOLDER_WIDTH + "&hei=" + URI_TEMPLATE_PLACEHOLDER_HEIGHT + "&fit=crop";
break;
case SCALE_WIDTH:
url = productionAssetUrl + DynamicMediaPath.buildImage(damContext)
+ "?wid=" + URI_TEMPLATE_PLACEHOLDER_WIDTH;
break;
case SCALE_HEIGHT:
url = productionAssetUrl + DynamicMediaPath.buildImage(damContext)
+ "?hei=" + URI_TEMPLATE_PLACEHOLDER_HEIGHT;
break;
default:
throw new IllegalArgumentException("Unsupported type: " + type);
}
NamedDimension smartCropDef = getDynamicMediaSmartCropDef(cropDimension, rotation, ratio, damContext);
url = buildUriTemplateDynamicMedia(type, cropDimension, rotation, smartCropDef, damContext);
// get actual max. dimension from smart crop rendition
if (url != null && smartCropDef != null) {
validatedDimension = SmartCrop.getCropDimensionForAsset(damContext.getAsset(), damContext.getResourceResolver(), smartCropDef);
}
}
if (url == null && (!damContext.isDynamicMediaEnabled() || !damContext.isDynamicMediaAemFallbackDisabled())) {
// Render renditions in AEM: build externalized URL
final long DUMMY_WIDTH = 999991;
final long DUMMY_HEIGHT = 999992;

String mediaPath = RenditionMetadata.buildMediaPath(damContext.getAsset().getOriginal().getPath() + "." + ImageFileServlet.SELECTOR
+ "." + DUMMY_WIDTH + "." + DUMMY_HEIGHT
+ "." + MediaFileServlet.EXTENSION,
ImageFileServlet.getImageFileName(damContext.getAsset().getName(), mediaArgs.getEnforceOutputFileExtension()));
UrlHandler urlHandler = AdaptTo.notNull(damContext, UrlHandler.class);
url = urlHandler.get(mediaPath).urlMode(mediaArgs.getUrlMode())
.buildExternalResourceUrl(damContext.getAsset().adaptTo(Resource.class));

switch (type) {
case CROP_CENTER:
url = StringUtils.replace(url, Long.toString(DUMMY_WIDTH), URI_TEMPLATE_PLACEHOLDER_WIDTH);
url = StringUtils.replace(url, Long.toString(DUMMY_HEIGHT), URI_TEMPLATE_PLACEHOLDER_HEIGHT);
break;
case SCALE_WIDTH:
url = StringUtils.replace(url, Long.toString(DUMMY_WIDTH), URI_TEMPLATE_PLACEHOLDER_WIDTH);
url = StringUtils.replace(url, Long.toString(DUMMY_HEIGHT), "0");
break;
case SCALE_HEIGHT:
url = StringUtils.replace(url, Long.toString(DUMMY_WIDTH), "0");
url = StringUtils.replace(url, Long.toString(DUMMY_HEIGHT), URI_TEMPLATE_PLACEHOLDER_HEIGHT);
break;
default:
throw new IllegalArgumentException("Unsupported type: " + type);
}
url = buildUriTemplateDam(type, rendition, cropDimension, rotation, damContext);
}
this.uriTemplate = url;

if (validatedDimension == null) {
validatedDimension = dimension;
}
this.dimension = validatedDimension;
}

private static String buildUriTemplateDam(@NotNull UriTemplateType type, @NotNull Rendition rendition,
@Nullable CropDimension cropDimension, @Nullable Integer rotation,
@NotNull DamContext damContext) {
final long DUMMY_WIDTH = 999991;
final long DUMMY_HEIGHT = 999992;

// build rendition URL with dummy width/height parameters (otherwise externalization will fail)
MediaArgs mediaArgs = damContext.getMediaArgs();
String mediaPath = RenditionMetadata.buildMediaPath(rendition.getPath()
+ "." + ImageFileServlet.buildSelectorString(DUMMY_WIDTH, DUMMY_HEIGHT, cropDimension, rotation, false)
+ "." + MediaFileServlet.EXTENSION,
ImageFileServlet.getImageFileName(damContext.getAsset().getName(), mediaArgs.getEnforceOutputFileExtension()));
UrlHandler urlHandler = AdaptTo.notNull(damContext, UrlHandler.class);
String url = urlHandler.get(mediaPath).urlMode(mediaArgs.getUrlMode())
.buildExternalResourceUrl(damContext.getAsset().adaptTo(Resource.class));

// replace dummy width/height parameters with actual placeholders
switch (type) {
case CROP_CENTER:
url = StringUtils.replace(url, Long.toString(DUMMY_WIDTH), URI_TEMPLATE_PLACEHOLDER_WIDTH);
url = StringUtils.replace(url, Long.toString(DUMMY_HEIGHT), URI_TEMPLATE_PLACEHOLDER_HEIGHT);
break;
case SCALE_WIDTH:
url = StringUtils.replace(url, Long.toString(DUMMY_WIDTH), URI_TEMPLATE_PLACEHOLDER_WIDTH);
url = StringUtils.replace(url, Long.toString(DUMMY_HEIGHT), "0");
break;
case SCALE_HEIGHT:
url = StringUtils.replace(url, Long.toString(DUMMY_WIDTH), "0");
url = StringUtils.replace(url, Long.toString(DUMMY_HEIGHT), URI_TEMPLATE_PLACEHOLDER_HEIGHT);
break;
default:
throw new IllegalArgumentException("Unsupported type: " + type);
}
return url;
}

@Override
public String getUriTemplate() {
return uriTemplate;
private static @Nullable String buildUriTemplateDynamicMedia(@NotNull UriTemplateType type,
@Nullable CropDimension cropDimension, @Nullable Integer rotation, @Nullable NamedDimension smartCropDef,
@NotNull DamContext damContext) {
String productionAssetUrl = damContext.getDynamicMediaServerUrl();
if (productionAssetUrl == null) {
return null;
}
StringBuilder result = new StringBuilder();
result.append(productionAssetUrl).append(DynamicMediaPath.buildImage(damContext));

// build DM URL with smart cropping
if (smartCropDef != null) {
result.append("%3A").append(smartCropDef.getName()).append("?")
.append(getDynamicMediaWidthHeightParameters(type))
.append("&fit=constrain");
return result.toString();
}

// build DM URL without smart cropping
result.append("?");
if (cropDimension != null) {
result.append("crop=").append(cropDimension.getCropStringWidthHeight()).append("&");
}
if (rotation != null) {
result.append("rotate=").append(rotation).append("&");
}
result.append(getDynamicMediaWidthHeightParameters(type));
return result.toString();
}

private static String getDynamicMediaWidthHeightParameters(UriTemplateType type) {
switch (type) {
case CROP_CENTER:
return "wid=" + URI_TEMPLATE_PLACEHOLDER_WIDTH + "&hei=" + URI_TEMPLATE_PLACEHOLDER_HEIGHT + "&fit=crop";
case SCALE_WIDTH:
return "wid=" + URI_TEMPLATE_PLACEHOLDER_WIDTH;
case SCALE_HEIGHT:
return "hei=" + URI_TEMPLATE_PLACEHOLDER_HEIGHT;
default:
throw new IllegalArgumentException("Unsupported type: " + type);
}
}

private static NamedDimension getDynamicMediaSmartCropDef(@Nullable CropDimension cropDimension, @Nullable Integer rotation,
@Nullable Double ratio, @NotNull DamContext damContext) {
if (SmartCrop.canApply(cropDimension, rotation) && ratio != null) {
// check for matching image profile and use predefined cropping preset if match found
return SmartCrop.getDimensionForRatio(damContext.getImageProfile(), ratio);
}
return null;
}

@Override
public UriTemplateType getType() {
return type;
}

@Override
public String getUriTemplate() {
return uriTemplate;
}

@Override
public long getMaxWidth() {
return dimension.getWidth();
Expand Down
Loading

0 comments on commit 4636128

Please sign in to comment.