Skip to content

Commit

Permalink
Dynamic Media with Open API: Use static cropping instead of Fastly-ba…
Browse files Browse the repository at this point in the history
…sed cropping if Smart Cropping is not available (#61)
  • Loading branch information
stefanseifert authored Aug 16, 2024
1 parent fb890ec commit b32824c
Show file tree
Hide file tree
Showing 14 changed files with 685 additions and 430 deletions.
6 changes: 6 additions & 0 deletions changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
<body>

<release version="2.1.2" date="not released">
<action type="add" dev="sseifert" issue="61">
Dynamic Media with OpenAPI: Use static cropping (center cropping) if named smart cropping based on Image Profile define in AEM is not available.
</action>
<action type="add" dev="sseifert" issue="61">
Dynamic Media with OpenAPI: Provide max. width/height in UriTemplate if source image dimension is available in metadata.
</action>
<action type="update" dev="sseifert">
Eliminate dependency to Commons Lang 2.
</action>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,12 @@ private String buildImageRenditionUrl() {
if (this.width > 0) {
params.width(this.width);
}
if (this.height > 0) {
params.height(this.height);
}
Dimension ratioDimension = MediaArgsDimension.getRequestedRatioAsWidthHeight(mediaArgs);
if (ratioDimension != null) {
params.cropSmartRatio(ratioDimension);
params.ratio(ratioDimension);
}

return new NextGenDynamicMediaImageUrlBuilder(context).build(params);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaContext;
import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaImageDeliveryParams;
import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaImageUrlBuilder;
import io.wcm.handler.mediasource.ngdm.impl.metadata.NextGenDynamicMediaMetadata;

/**
* {@link UriTemplate} implementation for Next Gen. Dynamic Media remote assets.
Expand All @@ -38,23 +39,34 @@ final class NextGenDynamicMediaUriTemplate implements UriTemplate {

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

NextGenDynamicMediaUriTemplate(@NotNull NextGenDynamicMediaContext context,
@NotNull UriTemplateType type) {
this.type = type;

if (type == UriTemplateType.SCALE_HEIGHT) {
throw new IllegalArgumentException("URI template type not supported: " + type);
NextGenDynamicMediaMetadata metadata = context.getMetadata();
if (metadata != null) {
dimension = metadata.getDimension();
}
else {
dimension = null;
}

NextGenDynamicMediaImageDeliveryParams params = new NextGenDynamicMediaImageDeliveryParams()
.widthPlaceholder(MediaNameConstants.URI_TEMPLATE_PLACEHOLDER_WIDTH)
.rotation(context.getMedia().getRotation())
.quality(ImageQualityPercentage.getAsInteger(context.getDefaultMediaArgs(), context.getMediaHandlerConfig()));

if (type == UriTemplateType.SCALE_HEIGHT) {
params.heightPlaceholder(MediaNameConstants.URI_TEMPLATE_PLACEHOLDER_HEIGHT);
}
else {
params.widthPlaceholder(MediaNameConstants.URI_TEMPLATE_PLACEHOLDER_WIDTH);
}

Dimension ratio = MediaArgsDimension.getRequestedRatioAsWidthHeight(context.getDefaultMediaArgs());
if (ratio != null) {
params.cropSmartRatio(ratio);
params.ratio(ratio);
}

this.uriTemplate = new NextGenDynamicMediaImageUrlBuilder(context).build(params);
Expand All @@ -72,11 +84,17 @@ final class NextGenDynamicMediaUriTemplate implements UriTemplate {

@Override
public long getMaxWidth() {
if (dimension != null) {
return dimension.getWidth();
}
return 0; // unknown
}

@Override
public long getMaxHeight() {
if (dimension != null) {
return dimension.getHeight();
}
return 0; // unknown
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ public class NextGenDynamicMediaImageDeliveryParams {

private Long width;
private String widthPlaceholder;
private Dimension cropSmartRatio;
private Long height;
private String heightPlaceholder;
private Dimension ratio;
private Integer rotation;
private Integer quality;

Expand Down Expand Up @@ -67,19 +69,51 @@ public class NextGenDynamicMediaImageDeliveryParams {
return this;
}

/**
* @return Height
*/
public @Nullable Long getHeight() {
return this.height;
}

/**
* @param value Height
* @return this
*/
public @NotNull NextGenDynamicMediaImageDeliveryParams height(@Nullable Long value) {
this.height = value;
return this;
}

/**
* @return Height placeholder
*/
public @Nullable String getHeightPlaceholder() {
return this.heightPlaceholder;
}

/**
* @param value Height placeholder
* @return this
*/
public @NotNull NextGenDynamicMediaImageDeliveryParams heightPlaceholder(@Nullable String value) {
this.heightPlaceholder = value;
return this;
}

/**
* @return Dimension with aspect ratio for smart cropping
*/
public @Nullable Dimension getCropSmartRatio() {
return this.cropSmartRatio;
public @Nullable Dimension getRatio() {
return this.ratio;
}

/**
* @param value Dimension with aspect ratio for smart cropping
* @return this
*/
public @NotNull NextGenDynamicMediaImageDeliveryParams cropSmartRatio(@Nullable Dimension value) {
this.cropSmartRatio = value;
public @NotNull NextGenDynamicMediaImageDeliveryParams ratio(@Nullable Dimension value) {
this.ratio = value;
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import io.wcm.handler.media.CropDimension;
import io.wcm.handler.media.Dimension;
import io.wcm.handler.media.format.Ratio;
import io.wcm.handler.media.impl.ImageTransformation;
import io.wcm.handler.mediasource.ngdm.impl.metadata.NextGenDynamicMediaMetadata;
import io.wcm.handler.mediasource.ngdm.impl.metadata.SmartCrop;
import io.wcm.wcm.commons.contenttype.FileExtension;
Expand Down Expand Up @@ -101,27 +103,17 @@ public NextGenDynamicMediaImageUrlBuilder(@NotNull NextGenDynamicMediaContext co
imageDeliveryPath = StringUtils.replace(imageDeliveryPath, PLACEHOLDER_FORMAT, format);

// prepare URL params
Long width = params.getWidth();
String widthPlaceholder = params.getWidthPlaceholder();
Dimension cropSmartRatio = params.getCropSmartRatio();
Integer rotation = params.getRotation();
Integer quality = params.getQuality();

SortedMap<String, String> urlParamMap = new TreeMap<>();
urlParamMap.put(PARAM_PREFER_WEBP, "true");
if (widthPlaceholder != null) {
urlParamMap.put(PARAM_WIDTH, widthPlaceholder);
}
else if (width != null) {
urlParamMap.put(PARAM_WIDTH, width.toString());
}
if (cropSmartRatio != null) {
boolean hasWidthDefined = width != null || widthPlaceholder != null;
applyCroppingParams(urlParamMap, cropSmartRatio, hasWidthDefined);
}

applyWidthHeightCroppingParams(params, urlParamMap);

Integer rotation = params.getRotation();
if (rotation != null && rotation != 0) {
urlParamMap.put(PARAM_ROTATE, rotation.toString());
}

Integer quality = params.getQuality();
if (quality != null) {
urlParamMap.put(PARAM_QUALITY, quality.toString());
}
Expand Down Expand Up @@ -171,15 +163,27 @@ else if (width != null) {
}

/**
* Generates cropping/smart cropping URL parameters with or without named smart crop.
* Apply URL parameters for cropping, width and height.
* @param params Parameters
* @param urlParamMap URL parameters
*/
private void applyCroppingParams(@NotNull SortedMap<String, String> urlParamMap,
@NotNull Dimension cropSmartRatio, boolean hasWidthDefined) {
SmartCrop namedSmartCrop = getMatchingNamedSmartCrop(cropSmartRatio);
@SuppressWarnings("java:S3776") // complexity
private void applyWidthHeightCroppingParams(@NotNull NextGenDynamicMediaImageDeliveryParams params, @NotNull SortedMap<String, String> urlParamMap) {
// get original image metadata
NextGenDynamicMediaMetadata metadata = context.getMetadata();
Dimension orginalDimension = null;
if (metadata != null) {
orginalDimension = metadata.getDimension();
}

// check for a matching named smart cropping profile
Dimension requestedRatio = params.getRatio();
SmartCrop namedSmartCrop = getMatchingNamedSmartCrop(metadata, requestedRatio);
if (namedSmartCrop != null) {
urlParamMap.put(PARAM_SMARTCROP, namedSmartCrop.getName());
if (!hasWidthDefined) {
// if no width given apply default width/height to not rely on dimensions defined in AEM image profile
boolean widthOrHeightDefined = applyWidthOrPlaceholder(params, urlParamMap) || applyHeightOrPlaceholder(params, urlParamMap);
if (!widthOrHeightDefined) {
// if no width or height given apply default width/height to not rely on dimensions defined in AEM image profile
String imageWidthHeightDefault = Long.toString(context.getNextGenDynamicMediaConfig().getImageWidthHeightDefault());
if (namedSmartCrop.getCropDimension().getWidth() >= namedSmartCrop.getCropDimension().getHeight()) {
urlParamMap.put(PARAM_WIDTH, imageWidthHeightDefault);
Expand All @@ -189,8 +193,33 @@ private void applyCroppingParams(@NotNull SortedMap<String, String> urlParamMap,
}
}
}
else if (orginalDimension != null && requestedRatio != null && isAutoCroppingRequired(orginalDimension, requestedRatio)) {
// apply static auto crop (center-cropping)
CropDimension cropDimension = ImageTransformation.calculateAutoCropDimension(
orginalDimension.getWidth(), orginalDimension.getHeight(), Ratio.get(requestedRatio));
urlParamMap.put(PARAM_CROP, cropDimension.getCropStringWidthHeight());
if (!applyWidthOrPlaceholder(params, urlParamMap)) {
applyHeightOrPlaceholder(params, urlParamMap);
}
}
else {
urlParamMap.put(PARAM_CROP, cropSmartRatio.getWidth() + ":" + cropSmartRatio.getHeight() + ",smart");
// No cropping required or insufficient metadata available to detect cropping
boolean widthDefined = applyWidthOrPlaceholder(params, urlParamMap);
boolean heightDefined = applyHeightOrPlaceholder(params, urlParamMap);
if (!(widthDefined || heightDefined) && requestedRatio != null) {
// if no width or height given apply default width/height respecting the requested aspect ratio
double ratio = Ratio.get(requestedRatio);
long width = context.getNextGenDynamicMediaConfig().getImageWidthHeightDefault();
long height = context.getNextGenDynamicMediaConfig().getImageWidthHeightDefault();
if (ratio > 1) {
height = Math.round(width / ratio);
}
else if (ratio < 1) {
width = Math.round(height * ratio);
}
urlParamMap.put(PARAM_WIDTH, Long.toString(width));
urlParamMap.put(PARAM_HEIGHT, Long.toString(height));
}
}
}

Expand All @@ -199,9 +228,8 @@ private void applyCroppingParams(@NotNull SortedMap<String, String> urlParamMap,
* @param cropSmartRatio Requested ratio
* @return Matching named smart crop or null if none found
*/
private @Nullable SmartCrop getMatchingNamedSmartCrop(@NotNull Dimension cropSmartRatio) {
NextGenDynamicMediaMetadata metadata = context.getMetadata();
if (metadata == null) {
private @Nullable SmartCrop getMatchingNamedSmartCrop(@Nullable NextGenDynamicMediaMetadata metadata, @Nullable Dimension cropSmartRatio) {
if (metadata == null || cropSmartRatio == null) {
return null;
}
double requestedRatio = Ratio.get(cropSmartRatio);
Expand All @@ -211,6 +239,58 @@ private void applyCroppingParams(@NotNull SortedMap<String, String> urlParamMap,
.orElse(null);
}

/**
* Checks if auto cropping is required.
* @param originalDimension Dimension of original image
* @param cropSmartRatio Requested aspect ratio
* @return true if auto cropping is required. False if original image matches the requested ratio.
*/
private boolean isAutoCroppingRequired(@NotNull Dimension originalDimension, @NotNull Dimension cropSmartRatio) {
return !Ratio.matches(Ratio.get(originalDimension), Ratio.get(cropSmartRatio));
}

/**
* Apply either width value or width placeholder, if available.
* @param params Parameters
* @param urlParamMap URL parameter map
* @return true if any width and/or height value or placeholder was applied
*/
private boolean applyWidthOrPlaceholder(@NotNull NextGenDynamicMediaImageDeliveryParams params, @NotNull SortedMap<String, String> urlParamMap) {
Long width = params.getWidth();
String widthPlaceholder = params.getWidthPlaceholder();
boolean anyApplied = false;
if (widthPlaceholder != null) {
urlParamMap.put(PARAM_WIDTH, widthPlaceholder);
anyApplied = true;
}
else if (width != null) {
urlParamMap.put(PARAM_WIDTH, width.toString());
anyApplied = true;
}
return anyApplied;
}

/**
* Apply either height value or height placeholder, if available.
* @param params Parameters
* @param urlParamMap URL parameter map
* @return true if any width and/or height value or placeholder was applied
*/
private boolean applyHeightOrPlaceholder(@NotNull NextGenDynamicMediaImageDeliveryParams params, @NotNull SortedMap<String, String> urlParamMap) {
Long height = params.getHeight();
String heightPlaceholder = params.getHeightPlaceholder();
boolean anyApplied = false;
if (heightPlaceholder != null) {
urlParamMap.put(PARAM_HEIGHT, heightPlaceholder);
anyApplied = true;
}
else if (height != null) {
urlParamMap.put(PARAM_HEIGHT, height.toString());
anyApplied = true;
}
return anyApplied;
}

/**
* @return Get file extension used for rendering via DM API.
*/
Expand Down
Loading

0 comments on commit b32824c

Please sign in to comment.