Skip to content

Commit

Permalink
SITES-13120 - Image Smart Crop support for remote assets in various S…
Browse files Browse the repository at this point in the history
…ites core components (#2524)

* add NextGen DM smart crop support.
* add button
* add alt
* add test
* fix css for aemcs
* update README.md
* add http test for smartcrop.
* add UI test for smart crop dialog.
* add UI test for NGDM smart crop.
* fix for aemcs

---------

Co-authored-by: igurjar <igurjar@adobe.com>
  • Loading branch information
indra2gurjar and igurjar authored Jul 19, 2023
1 parent ce27843 commit b49e22c
Show file tree
Hide file tree
Showing 31 changed files with 1,209 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,7 @@ public class ImageImpl extends com.adobe.cq.wcm.core.components.internal.models.
private static final String URI_WIDTH_PLACEHOLDER_ENCODED = "%7B.width%7D";
private static final String URI_WIDTH_PLACEHOLDER = "{.width}";
private static final String EMPTY_PIXEL = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";

private static final String PATH_PLACEHOLDER_ASSET_ID = "{asset-id}";
private static final String PATH_PLACEHOLDER_SEO_NAME = "{seo-name}";
private static final String PATH_PLACEHOLDER_FORMAT = "{format}";
private static final String DEFAULT_NGDM_ASSET_EXTENSION = "jpg";
private static final int DEFAULT_NGDM_ASSET_WIDTH = 640;
static final int DEFAULT_NGDM_ASSET_WIDTH = 640;

@Inject
@Optional
Expand Down Expand Up @@ -277,22 +272,17 @@ private void initNextGenerationDynamicMedia() {
initResource();
properties = resource.getValueMap();
String fileReference = properties.get("fileReference", String.class);
String smartCrop = properties.get("smartCrop", String.class);
if (isNgdmImageReference(fileReference)) {
Scanner scanner = new Scanner(fileReference);
scanner.useDelimiter("/");
String assetId = scanner.next();
scanner = new Scanner(scanner.next());
scanner.useDelimiter("\\.");
String assetName = scanner.hasNext() ? scanner.next() : assetId;
String assetExtension = scanner.hasNext() ? scanner.next() : DEFAULT_NGDM_ASSET_EXTENSION;
String imageDeliveryBasePath = nextGenDynamicMediaConfig.getImageDeliveryBasePath();
String imageDeliveryPath = imageDeliveryBasePath.replace(PATH_PLACEHOLDER_ASSET_ID, assetId);
imageDeliveryPath = imageDeliveryPath.replace(PATH_PLACEHOLDER_SEO_NAME, assetName);
imageDeliveryPath = imageDeliveryPath.replace(PATH_PLACEHOLDER_FORMAT, assetExtension);
ngdmImage = true;
int width = currentStyle.get(PN_DESIGN_RESIZE_WIDTH, DEFAULT_NGDM_ASSET_WIDTH);
String repositoryId = nextGenDynamicMediaConfig.getRepositoryId();
src = "https://" + repositoryId + imageDeliveryPath + "?width=" + width + "&preferwebp=true";
NextGenDMImageURIBuilder builder = new NextGenDMImageURIBuilder(nextGenDynamicMediaConfig, fileReference)
.withPreferWebp(true)
.withWidth(width);
if(StringUtils.isNotEmpty(smartCrop)) {
builder.withSmartCrop(smartCrop);
}
src = builder.build();
ngdmImage = true;
hasContent = true;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ Copyright 2023 Adobe
~
~ 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.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
package com.adobe.cq.wcm.core.components.internal.models.v3;

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.cq.ui.wcm.commons.config.NextGenDynamicMediaConfig;

import static com.adobe.cq.wcm.core.components.internal.models.v3.ImageImpl.DEFAULT_NGDM_ASSET_WIDTH;

public class NextGenDMImageURIBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(NextGenDMImageURIBuilder.class);
private static final String PATH_PLACEHOLDER_ASSET_ID = "{asset-id}";
private static final String PATH_PLACEHOLDER_SEO_NAME = "{seo-name}";
private static final String PATH_PLACEHOLDER_FORMAT = "{format}";
private static final String DEFAULT_NGDM_ASSET_EXTENSION = "jpg";

private NextGenDynamicMediaConfig config;
private String fileReference;
private String smartCropAspectRatio;
private int width = DEFAULT_NGDM_ASSET_WIDTH;

private int height;
private boolean preferWebp = true;

public NextGenDMImageURIBuilder(NextGenDynamicMediaConfig config, String fileReference) {
this.config = config;
this.fileReference = fileReference;
}

/**
* Smart Crop aspect ratio string.
* @param smartCropAspectRatio - a string in "width:height" format;
*/
public NextGenDMImageURIBuilder withSmartCrop(String smartCropAspectRatio) {
this.smartCropAspectRatio = smartCropAspectRatio;
return this;
}

/**
* Image width
* @param width - an integer.
*/
public NextGenDMImageURIBuilder withWidth(int width) {
this.width = width;
return this;
}

/**
* Image height
* @param height - an integer.
*/
public NextGenDMImageURIBuilder withHeight(int height) {
this.height = height;
return this;
}


/**
* Set to use webp image format.
* @param preferWebp - should set preferwebp param.
*/
public NextGenDMImageURIBuilder withPreferWebp(boolean preferWebp) {
this.preferWebp = preferWebp;
return this;
}

/**
* Use this to create a NextGen Dynamic Media Image URI.
* @return a uri.
*/
public String build() {
if(StringUtils.isNotEmpty(this.fileReference) && this.config != null) {
Scanner scanner = new Scanner(this.fileReference);
scanner.useDelimiter("/");
String assetId = scanner.next();
scanner = new Scanner(scanner.next());
scanner.useDelimiter("\\.");
String assetName = scanner.hasNext() ? scanner.next() : assetId;
String assetExtension = scanner.hasNext() ? scanner.next() : DEFAULT_NGDM_ASSET_EXTENSION;
String imageDeliveryBasePath = this.config.getImageDeliveryBasePath();
String imageDeliveryPath = imageDeliveryBasePath.replace(PATH_PLACEHOLDER_ASSET_ID, assetId);
imageDeliveryPath = imageDeliveryPath.replace(PATH_PLACEHOLDER_SEO_NAME, assetName);
imageDeliveryPath = imageDeliveryPath.replace(PATH_PLACEHOLDER_FORMAT, assetExtension);
String repositoryId = this.config.getRepositoryId();
StringBuilder uriBuilder = new StringBuilder("https://" + repositoryId + imageDeliveryPath);
Map<String, String> params = new HashMap<>();
if(this.width > 0) {
params.put("width", Integer.toString(this.width));
}
if(this.height > 0) {
params.put("height", Integer.toString(this.height));
}
if(this.preferWebp) {
params.put("preferwebp", "true");
}
if (StringUtils.isNotEmpty(this.smartCropAspectRatio)) {
if (isValidSmartCrop(this.smartCropAspectRatio)) {
params.put("crop", String.format("%s,smart", this.smartCropAspectRatio));
} else {
LOGGER.info("Invalid smartCrop value at {}", this.smartCropAspectRatio);
}
}
if(params.size() > 0) {
uriBuilder.append("?");
for(Map.Entry<String, String> entry: params.entrySet()) {
uriBuilder.append(entry.getKey() + "=" + entry.getValue());
uriBuilder.append("&");
}
uriBuilder.deleteCharAt(uriBuilder.length() - 1);
}
return uriBuilder.toString();
}
LOGGER.info("Invalid fileReference or NGDMConfig. fileReference = {}", this.fileReference);
return null;
}

private boolean isValidSmartCrop(String smartCropStr) {
String[] crops = smartCropStr.split(":");
if (crops.length == 2) {
try {
Integer.parseInt(crops[0]);
Integer.parseInt(crops[1]);
return true;
} catch (NumberFormatException ex) {
return false;
}
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ Copyright 2023 Adobe
~
~ 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.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
package com.adobe.cq.wcm.core.components.internal.models.v3;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Optional;
import org.apache.sling.models.annotations.injectorspecific.Self;
import org.apache.sling.models.annotations.injectorspecific.SlingObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.cq.export.json.ExporterConstants;
import com.adobe.cq.ui.wcm.commons.config.NextGenDynamicMediaConfig;
import com.adobe.cq.wcm.core.components.models.nextgendm.NextGenDMThumbnail;

import static com.adobe.cq.wcm.core.components.internal.models.v3.ImageImpl.isNgdmImageReference;

@Model(adaptables = SlingHttpServletRequest.class,
adapters = {NextGenDMThumbnail.class},
resourceType = NextGenDMThumbnailImpl.RESOURCE_TYPE)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class NextGenDMThumbnailImpl implements NextGenDMThumbnail {

private static final Logger LOGGER = LoggerFactory.getLogger(NextGenDMThumbnailImpl.class);
protected static final String RESOURCE_TYPE = "core/wcm/components/image/v3/image/nextgendmthumbnail";

/**
* The current resource.
*/
@SlingObject
protected Resource resource;

@Self
private SlingHttpServletRequest request;

@Inject
@Optional
private NextGenDynamicMediaConfig nextGenDynamicMediaConfig;

private String componentPath;

private String src;

private String altText = "";

@PostConstruct
private void initModel() {
componentPath = request.getRequestPathInfo().getSuffix();
Resource component = request.getResourceResolver().getResource(componentPath);
ValueMap properties = component.getValueMap();
String fileReference = properties.get("fileReference", String.class);
String smartCrop = properties.get("smartCrop", String.class);
ValueMap configs = resource.getValueMap();
int width = configs.get("width", 480);
int height = configs.get("height", 480);
altText = configs.get("alt", "image thumbnail");
if (isNgdmImageReference(fileReference)) {
NextGenDMImageURIBuilder builder = new NextGenDMImageURIBuilder(nextGenDynamicMediaConfig, fileReference)
.withPreferWebp(true)
.withWidth(width)
.withHeight(height);
if (StringUtils.isNotEmpty(smartCrop)) {
builder.withSmartCrop(smartCrop);
}
this.src = builder.build();
}
}

@Override
public String getSrc() {
return src;
}

@Override
public String getAlt() {
return altText;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ Copyright 2023 Adobe
~
~ 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.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
package com.adobe.cq.wcm.core.components.models.nextgendm;

import java.util.HashMap;
import java.util.Map;

public interface NextGenDMThumbnail {
default public String getSrc() {
return null;
}

default public String getAlt() {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ protected void testSimpleDecorativeImage() {
protected void testImageWithTwoOrMoreSmartSizes() {
context.contentPolicyMapping(resourceType,
"allowedRenditionWidths", new int[]{600, 700, 800, 2000, 2500},
"sizes", new String[]{"(max-width: 600px) 480px", "800px"});
"sizes", new String[]{"(max-width: 600px) 480px", "800px"});
String escapedResourcePath = AbstractImageTest.IMAGE0_PATH.replace("jcr:content", "_jcr_content");
Image image = getImageUnderTest(AbstractImageTest.IMAGE0_PATH);
assertEquals("Adobe Systems Logo and Wordmark in PNG format", image.getAlt());
Expand Down Expand Up @@ -306,7 +306,7 @@ protected void testEmptyImageDelegatingToFeaturedImage() {
protected void testImageWithMoreThanOneSmartSize() {
context.contentPolicyMapping(resourceType,
"allowedRenditionWidths", new int[]{600, 700, 800, 2000, 2500},
"sizes", new String[]{"(max-width: 600px) 480px", "800px"});
"sizes", new String[]{"(max-width: 600px) 480px", "800px"});
Image image = getImageUnderTest(AbstractImageTest.IMAGE0_PATH);
assertArrayEquals(new int[]{600, 700, 800, 2000, 2500}, image.getWidths());
assertTrue(image.isLazyEnabled());
Expand All @@ -329,8 +329,8 @@ protected void testImageWithNoSmartSize() {
@Test
protected void testGetUuid() {
context.contentPolicyMapping(resourceType,
"allowedRenditionWidths", new int[]{600, 700, 800, 2000, 2500},
"sizes", new String[]{"(max-width: 600px) 480px", "800px"});
"allowedRenditionWidths", new int[]{600, 700, 800, 2000, 2500},
"sizes", new String[]{"(max-width: 600px) 480px", "800px"});
Image image = getImageUnderTest(AbstractImageTest.IMAGE0_PATH);
assertEquals("60a1a56e-f3f4-4021-a7bf-ac7a51f0ffe5", image.getUuid());
Utils.testJSONExport(image, Utils.getTestExporterJSONPath(testBase, AbstractImageTest.IMAGE0_PATH));
Expand Down Expand Up @@ -652,7 +652,7 @@ void testSrcSetWithAssetDeliveryEnabledWithSmartSizes() {
"allowedRenditionWidths", new int[]{600, 800});
Image image = getImageUnderTest(IMAGE0_PATH);
String expectedSrcSet = MockAssetDelivery.BASE_URL + IMAGE_FILE_REFERENCE + "." + ASSET_NAME + ".png?width=600&quality=82&preferwebp=true 600w," +
MockAssetDelivery.BASE_URL + IMAGE_FILE_REFERENCE + "." + ASSET_NAME + ".png?width=800&quality=82&preferwebp=true 800w";
MockAssetDelivery.BASE_URL + IMAGE_FILE_REFERENCE + "." + ASSET_NAME + ".png?width=800&quality=82&preferwebp=true 800w";
assertEquals(expectedSrcSet , image.getSrcset());
}

Expand Down
Loading

0 comments on commit b49e22c

Please sign in to comment.