Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanseifert committed Aug 26, 2024
2 parents 9cc0608 + 0634e11 commit 51699da
Show file tree
Hide file tree
Showing 39 changed files with 1,059 additions and 495 deletions.
24 changes: 24 additions & 0 deletions changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,30 @@
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="2.2.0" date="2024-08-26">
<action type="update" dev="cnagel" issue="62">
Web-Optimized Image Delivery: Use relative (percentage) parameters for cropping instead of absolute parameters. This should create more reliable renditions esp. for original images in low resolution.
You can switch back to the old behavior by setting "Crop Option" to ABSOLUTE_PARAMETERS in the "wcm.io Media Handler Web-Optimized Image Delivery Support" OSGi configuration.
</action>
<action type="update" 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="update" 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" issue="64"><![CDATA[
Dynamic Media with OpenAPI: Do not enable support for remote assets by default. Since general availability the related configuration services not longer protected by a feature flag, so the feature has to be enabled explicitly via OSGi configuration.<br/>
<b>Breaking change:</b> You can enable support for remote assets by setting "Remote Assets" to true in the "wcm.io Media Handler Dynamic Media with OpenAPI Support" OSGi configuration.
]]></action>
<action type="update" dev="sseifert" issue="65"><![CDATA[
Dynamic Media: Control image quality for lossy output image for each media request by appending "qlt" URL parameter, defaulting to the default image quality configured in the media handler configuration class.<br/>
<b>Breaking change:</b> You can disable this behavior by setting "Set Image Quality" to false in the "wcm.io Media Handler Dynamic Media Support" OSGi configuration. If disabled, the default image quality setting configured in Dynamic Media is used for all images.
]]></action>
<action type="update" dev="sseifert">
Eliminate dependency to Commons Lang 2.
</action>
</release>

<release version="2.1.0" date="2024-07-08">
<action type="add" dev="sseifert" issue="52">
Dynamic Media with OpenAPI: Support Dynamic Media with OpenAPI also for local assets.
Expand Down
8 changes: 4 additions & 4 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>2.1.0</version>
<version>2.2.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>2024-07-08T08:50:40Z</project.build.outputTimestamp>
<project.build.outputTimestamp>2024-08-26T09:45:12Z</project.build.outputTimestamp>
</properties>

<dependencies>
Expand All @@ -76,7 +76,7 @@
<dependency>
<groupId>io.wcm</groupId>
<artifactId>io.wcm.sling.models</artifactId>
<version>1.6.0</version>
<version>2.0.0</version>
<scope>compile</scope>
</dependency>
<dependency>
Expand Down Expand Up @@ -144,7 +144,7 @@
<dependency>
<groupId>io.wcm</groupId>
<artifactId>io.wcm.testing.aem-mock.junit5</artifactId>
<version>5.5.0</version>
<version>5.6.0</version>
<scope>test</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@
* limitations under the License.
* #L%
*/
package io.wcm.handler.mediasource.ngdm.impl;
package io.wcm.handler.media.impl;

import org.jetbrains.annotations.NotNull;

import io.wcm.handler.media.MediaArgs;
import io.wcm.handler.media.spi.MediaHandlerConfig;

/**
* Sanitizes SEO names for usage in context of Next Gen. Dynamic Media
* Gets image quality for current media request, with fallback to default quality.
*/
public final class ImageQualityPercentage {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@

import io.wcm.handler.media.Dimension;
import io.wcm.handler.media.MediaArgs;
import io.wcm.handler.media.impl.ImageQualityPercentage;
import io.wcm.handler.media.spi.MediaHandlerConfig;
import io.wcm.handler.mediasource.dam.impl.dynamicmedia.DynamicMediaSupportService;
import io.wcm.handler.mediasource.dam.impl.dynamicmedia.ImageProfile;
import io.wcm.handler.mediasource.dam.impl.dynamicmedia.NamedDimension;
import io.wcm.handler.mediasource.dam.impl.weboptimized.WebOptimizedImageDeliveryParams;
import io.wcm.handler.mediasource.dam.impl.weboptimized.WebOptimizedImageDeliveryService;
import io.wcm.handler.mediasource.ngdm.impl.ImageQualityPercentage;

/**
* Context objects require in DAM support implementation.
Expand Down Expand Up @@ -163,6 +163,14 @@ public boolean isDynamicMediaValidateSmartCropRenditionSizes() {
return dynamicMediaSupportService.isValidateSmartCropRenditionSizes();
}

/**
* @return Whether to control image quality for lossy output formats for each media request via 'qlt' URL parameter
* (instead of relying on default setting within Dynamic Media).
*/
public boolean isDynamicMediaSetImageQuality() {
return dynamicMediaSupportService.isSetImageQuality();
}

/**
* @return Dynamic media reply image size limit
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
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.ImageQualityPercentage;
import io.wcm.handler.mediasource.dam.impl.DamContext;
import io.wcm.wcm.commons.contenttype.ContentType;

Expand Down Expand Up @@ -126,15 +127,8 @@ private DynamicMediaPath() {
logResult(damContext, "<too small for " + width + "x" + height + ">");
return null;
}
result.append("%3A").append(smartCropDef.getName()).append("?")
.append("wid=").append(dimension.getWidth()).append("&")
.append("hei=").append(dimension.getHeight()).append("&")
// cropping/width/height is pre-calculated to fit with original ratio, make sure there are no 1px background lines visible
.append("fit=stretch");
if (isPNG(damContext)) {
// if original image is PNG image, make sure alpha channel is preserved
result.append("&fmt=png-alpha");
}
result.append("%3A").append(smartCropDef.getName()).append("?");
appendWidthHeigtFormatQuality(result, dimension, damContext);
logResult(damContext, result);
return result.toString();
}
Expand All @@ -147,6 +141,12 @@ private DynamicMediaPath() {
if (rotation != null) {
result.append("rotate=").append(rotation).append("&");
}
appendWidthHeigtFormatQuality(result, dimension, damContext);
logResult(damContext, result);
return result.toString();
}

private static void appendWidthHeigtFormatQuality(@NotNull StringBuilder result, @NotNull Dimension dimension, @NotNull DamContext damContext) {
result.append("wid=").append(dimension.getWidth()).append("&")
.append("hei=").append(dimension.getHeight()).append("&")
// cropping/width/height is pre-calculated to fit with original ratio, make sure there are no 1px background lines visible
Expand All @@ -155,8 +155,10 @@ private DynamicMediaPath() {
// if original image is PNG image, make sure alpha channel is preserved
result.append("&fmt=png-alpha");
}
logResult(damContext, result);
return result.toString();
else if (damContext.isDynamicMediaSetImageQuality()) {
// it not PNG lossy format is used, apply image quality setting
result.append("&qlt=").append(ImageQualityPercentage.getAsInteger(damContext.getMediaArgs(), damContext.getMediaHandlerConfig()));
}
}

private static void logResult(@NotNull DamContext damContext, @NotNull CharSequence result) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ public interface DynamicMediaSupportService {
@NotNull
Dimension getImageSizeLimit();

/**
* @return Whether to control image quality for lossy output formats for each media request via 'qlt' URL parameter
* (instead of relying on default setting within Dynamic Media).
*/
boolean isSetImageQuality();

/**
* Get image profile.
* @param profilePath Full profile path
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ public class DynamicMediaSupportServiceImpl implements DynamicMediaSupportServic
description = "The configured height value for 'Reply Image Size Limit'.")
long imageSizeLimitHeight() default 2000;

@AttributeDefinition(
name = "Set Image Quality",
description = "Control image quality for lossy output formats for each media request via 'qlt' URL parameter (instead of relying on default setting within Dynamic Media).")
boolean setImageQuality() default true;

}

@Reference
Expand All @@ -118,6 +123,7 @@ public class DynamicMediaSupportServiceImpl implements DynamicMediaSupportServic
private boolean disableAemFallback;
private boolean validateSmartCropRenditionSizes;
private Dimension imageSizeLimit;
private boolean setImageQuality;

private static final String SERVICEUSER_SUBSERVICE = "dynamic-media-support";
private static final Pattern DAM_PATH_PATTERN = Pattern.compile("^/content/dam(/.*)?$");
Expand All @@ -132,6 +138,7 @@ private void activate(Config config) {
this.disableAemFallback = config.disableAemFallback();
this.validateSmartCropRenditionSizes = config.validateSmartCropRenditionSizes();
this.imageSizeLimit = new Dimension(config.imageSizeLimitWidth(), config.imageSizeLimitHeight());
this.setImageQuality = config.setImageQuality();

if (this.enabled) {
log.info("DynamicMediaSupport: enabled={}, capabilityEnabled={}, capabilityDetection={}, "
Expand Down Expand Up @@ -174,6 +181,11 @@ public boolean isValidateSmartCropRenditionSizes() {
return this.imageSizeLimit;
}

@Override
public boolean isSetImageQuality() {
return setImageQuality;
}

@Override
public @Nullable ImageProfile getImageProfile(@NotNull String profilePath) {
try (ResourceResolver resourceResolver = resourceResolverFactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,14 @@
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

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

import io.wcm.handler.media.CropDimension;
import io.wcm.handler.media.Dimension;
import io.wcm.handler.mediasource.dam.AssetRendition;
import io.wcm.wcm.commons.contenttype.FileExtension;

final class ParameterMap {
Expand All @@ -57,7 +61,8 @@ private ParameterMap() {
}

@NotNull
static Map<String, Object> build(@NotNull Asset asset, @NotNull WebOptimizedImageDeliveryParams params) {
static Map<String, Object> build(@NotNull Asset asset, @NotNull WebOptimizedImageDeliveryParams params,
@NotNull WebOptimizedImageDeliveryCropOption cropOption) {
String path = asset.getPath();
String seoName = FilenameUtils.getBaseName(asset.getName());
String format = StringUtils.toRootLowerCase(FilenameUtils.getExtension(asset.getName()));
Expand All @@ -81,7 +86,7 @@ static Map<String, Object> build(@NotNull Asset asset, @NotNull WebOptimizedImag
map.put(PARAM_WIDTH, width.toString());
}
if (cropDimension != null) {
map.put(PARAM_CROP, cropDimension.getCropStringWidthHeight());
map.put(PARAM_CROP, createCroppingString(asset, cropDimension, cropOption));
}
if (rotation != null && rotation != 0) {
map.put(PARAM_ROTATE, rotation.toString());
Expand All @@ -92,4 +97,22 @@ static Map<String, Object> build(@NotNull Asset asset, @NotNull WebOptimizedImag
return map;
}

private static @NotNull String createCroppingString(@NotNull Asset asset, @NotNull CropDimension cropDimension,
@NotNull WebOptimizedImageDeliveryCropOption cropOption) {
if (cropOption == WebOptimizedImageDeliveryCropOption.RELATIVE_PARAMETERS) {
Dimension imageDimension = loadImageDimension(asset);
if (imageDimension != null && imageDimension.getWidth() > 0 && imageDimension.getHeight() > 0) {
return RelativeCroppingString.createFromCropDimension(cropDimension, imageDimension);
}
}
return cropDimension.getCropStringWidthHeight();
}

private static @Nullable Dimension loadImageDimension(@NotNull Asset asset) {
Rendition originalRendition = asset.getOriginal();
return originalRendition == null
? null
: AssetRendition.getDimension(originalRendition);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* #%L
* wcm.io
* %%
* Copyright (C) 2024 wcm.io
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package io.wcm.handler.mediasource.dam.impl.weboptimized;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Locale;

import org.jetbrains.annotations.NotNull;

import io.wcm.handler.media.CropDimension;
import io.wcm.handler.media.Dimension;

/**
* Creates relative crop string with percentage values as required by the Web-Optimized Image Delivery API.
* It uses one fractional digit for the percentage values.
*/
final class RelativeCroppingString {

private static final NumberFormat DECIMAL_FORMAT = new DecimalFormat("0.0", new DecimalFormatSymbols(Locale.US));

private RelativeCroppingString() {
// static methods only
}

static @NotNull String createFromCropDimension(
@NotNull CropDimension cropDimension, @NotNull Dimension imageDimension) {
double x1 = cropDimension.getLeft();
double y1 = cropDimension.getTop();
double left = x1 / imageDimension.getWidth();
double top = y1 / imageDimension.getHeight();
double width = (double)cropDimension.getWidth() / imageDimension.getWidth();
double height = (double)cropDimension.getHeight() / imageDimension.getHeight();
return create(left, top, width, height);
}

static @NotNull String create(double left, double top, double width, double height) {
return String.format("%sp,%sp,%sp,%sp",
toPercentage(left), toPercentage(top),
toPercentage(width), toPercentage(height));
}

private static String toPercentage(double fraction) {
double percentage = Math.round(fraction * 1000d) / 10d;
percentage = Math.max(0.0, percentage);
percentage = Math.min(100.0, percentage);
return DECIMAL_FORMAT.format(percentage);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* #%L
* wcm.io
* %%
* Copyright (C) 2024 wcm.io
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package io.wcm.handler.mediasource.dam.impl.weboptimized;

/**
* Choose how to calculate/apply cropping parameter when using Web-Optimized image delivery.
*/
public enum WebOptimizedImageDeliveryCropOption {

/**
* Crop renditions using relative percentage values as parameters (e.g. crop=0.0p,5.0p,100.0p,80.0p),
* based on the original image dimensions.
*/
RELATIVE_PARAMETERS,

/**
* Crop renditions using absolute pixel values as parameters (e.g. crop=0,10,200,100),
* based on the original image dimensions.
*/
ABSOLUTE_PARAMETERS

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ public interface WebOptimizedImageDeliveryService {
*/
boolean isEnabled();

/**
* Crop option to use.
* @return Crop option
*/
WebOptimizedImageDeliveryCropOption getCropOption();

/**
* Get delivery URL for a rendition of an asset.
* @param asset Asset
Expand Down
Loading

0 comments on commit 51699da

Please sign in to comment.