Quantcast
Channel: Experiencing Adobe Experience Manager (AEM, CQ)
Viewing all articles
Browse latest Browse all 525

HTML 5 Smart Image Component Custom Aspect Ratios

$
0
0

Goal


Create a html 5 smart image component that supports custom aspect ratios, image cropping. An author can create images of different aspect ratios using the same image component. Before you proceed, the process explained here is not Adobe suggested approach, it's just a thought; Package install, Source codeand Video demonstration are available for download..


Prerequisites


If you are new to CQ pls visit this blog post; it explains the page component basics and setting up your IDE

Create the Component


1) In your CRXDE Lite http://localhost:4502/crx/de, create the below folder and save changes

                      /apps/imagecustomar

2) Copy the component /libs/foundation/components/logo and paste it in path /apps/imagecustomar

3) Rename /apps/imagecustomar/logo to /apps/imagecustomar/image

4) Rename /apps/imagecustomar/image/logo.jsp to /apps/imagecustomar/image/image.jsp

5) Change the package statement of /apps/imagecustomar/image/img.GET.java from libs.foundation.components.logo to apps.imagecustomar.image

6) Change the following properties of /apps/imagecustomar/image

                     componentGroup - My Components
                     jcr:title - Image with Custom Aspect Ratios

7) Rename /apps/imagecustomar/image/design_dialog to /apps/imagecustomar/image/dialog

8) Delete /apps/imagecustomar/image/dialog/items/basic

9) Replace the code in /apps/imagecustomar/image/image.jsp with the following...

<%@include file="/libs/foundation/global.jsp" %>
<%@ page import="com.day.cq.commons.Doctype,
com.day.cq.wcm.foundation.Image,
java.io.PrintWriter" %>
<%
try {
Resource res = null;

if (currentNode.hasProperty("imageReference")) {
res = resource;
}
%>

<%
if (res == null) {
%>
Configure Image
<%
} else {
Image img = new Image(res);
img.setItemName(Image.NN_FILE, "image");
img.setItemName(Image.PN_REFERENCE, "imageReference");
img.setSelector("img");
img.setDoctype(Doctype.fromRequest(request));
img.setAlt("Home");
img.draw(out);
}
} catch (Exception e) {
e.printStackTrace(new PrintWriter(out));
}
%>


10) Add this Image component on a page and select an image. The image dialog with cropping features enabled looks like below. Here, Free crop has default aspect ratio (0,0)


10) We now have a basic image component with ootb features ( crop, rotate etc ). Let us modify this component to add custom aspect ratios; in the process we also create and register a new ExtJS xtype

Create and Register xtype


1) Create the node /apps/imagecustomar/image/clientlib of type cq:ClientLibraryFolder and add the following properties

             categories - String[] - mycomponent.imagear
             dependencies - String[] - cq.widgets

2) Create file (type nt:file) /apps/imagecustomar/image/clientlib/js.txt and add the following

              imagear.js

3) Create file /apps/imagecustomar/image/clientlib/imagear.js. For now, add the following code

              alert("hi");

4) Modify /apps/imagecustomar/image/image.jsp to include the clientlib created above

               <cq:includeClientLib categories="mycomponent.imagear"/>

5) Save changes and access the component dialog; an alert "hi" should popup, confirming the js file load.

6) Add the following JS code to imagear.js. This logic adds the custom aspect ratios UI changes to crop tool

var MyClientLib = MyClientLib || {};

MyClientLib.Html5SmartImage = CQ.Ext.extend(CQ.html5.form.SmartImage, {
crops: {},

constructor: function (config) {
config = config || {};

var aRatios = {
"freeCrop": {
"value": "0,0",
"text": CQ.I18n.getMessage("Free crop")
}
};

var tObj = this;

$.each(config, function (key, value) {
if (key.endsWith("AspectRatio")) {
var text = config[key + "Text"];

if (!text) {
text = key;
}

if (!value) {
value = "0,0";
}

aRatios[key] = {
"value": value,
"text": text
};

tObj.crops[key] = { text: text, cords : ''};
}
});

var defaults = { "cropConfig": { "aspectRatios": aRatios } };
config = CQ.Util.applyDefaults(config, defaults);

MyClientLib.Html5SmartImage.superclass.constructor.call(this, config);
},

initComponent: function () {
MyClientLib.Html5SmartImage.superclass.initComponent.call(this);

var imgTools = this.imageToolDefs;
var cropTool;

if(imgTools){
for(var x = 0; x < imgTools.length; x++){
if(imgTools[x].toolId == 'smartimageCrop'){
cropTool = imgTools[x];
break;
}
}
}

if(!cropTool){
return;
}

for(var x in this.crops){
if(this.crops.hasOwnProperty(x)){
var field = new CQ.Ext.form.Hidden({
id: x,
name: "./" + x
});

this.add(field);

field = new CQ.Ext.form.Hidden({
name: "./" + x + "Text",
value: this.crops[x].text
});

this.add(field);
}
}

var userInterface = cropTool.userInterface;

this.on("loadimage", function(){
var aRatios = userInterface.aspectRatioMenu.findByType("menucheckitem");

if(!aRatios){
return;
}

for(var x = 0; x < aRatios.length; x++){
if(aRatios[x].text !== "Free crop"){
aRatios[x].on('click', function(radio){
var key = this.getCropKey(radio.text);

if(!key){
return;
}

if(this.crops[key].cords){
this.setCoords(cropTool, this.crops[key].cords);
}else{
var field = CQ.Ext.getCmp(key);
this.crops[key].cords = this.getRect(radio, userInterface);
field.setValue(this.crops[key].cords);
}
},this);
}

var key = this.getCropKey(aRatios[x].text);

if(key && this.dataRecord && this.dataRecord.data[key]){
this.crops[key].cords = this.dataRecord.data[key];

var field = CQ.Ext.getCmp(key);
field.setValue(this.crops[key].cords);
}
}
});

cropTool.workingArea.on("contentchange", function(changeDef){
var aRatios = userInterface.aspectRatioMenu.findByType("menucheckitem");
var aRatioChecked;

if(aRatios){
for(var x = 0; x < aRatios.length; x++){
if(aRatios[x].checked === true){
aRatioChecked = aRatios[x];
break;
}
}
}

if(!aRatioChecked){
return;
}

var key = this.getCropKey(aRatioChecked.text);
var field = CQ.Ext.getCmp(key);

this.crops[key].cords = this.getRect(aRatioChecked, userInterface);
field.setValue(this.crops[key].cords);
}, this);
},

getCropKey: function(text){
for(var x in this.crops){
if(this.crops.hasOwnProperty(x)){
if(this.crops[x].text == text){
return x;
}
}
}

return null;
},

getRect: function (radio, ui) {
var ratioStr = "";
var aspectRatio = radio.value;

if ((aspectRatio != null) && (aspectRatio != "0,0")) {
ratioStr = "/" + aspectRatio;
}

if (ui.cropRect == null) {
return ratioStr;
}

return ui.cropRect.x + "," + ui.cropRect.y + "," + (ui.cropRect.x + ui.cropRect.width) + ","
+ (ui.cropRect.y + ui.cropRect.height) + ratioStr;
},

setCoords: function (cropTool, cords) {
cropTool.initialValue = cords;
cropTool.onActivation();
}
});

CQ.Ext.reg("myhtml5smartimage", MyClientLib.Html5SmartImage);


7) To create the following image source paths...

/content/firstapp-demo-site/test/_jcr_content/par/image.img.png/2To1AspectRatio.jpg
/content/firstapp-demo-site/test/_jcr_content/par/image.img.png/9To1AspectRatio.jpg
/content/firstapp-demo-site/test/_jcr_content/par/image.img.png/5To1AspectRatio.jpg

Replace the code in /apps/imagecustomar/image/image.jsp with below code. This jsp renders the cropped custom aspect ratio images...

<%@include file="/libs/foundation/global.jsp" %>
<%@ page import="com.day.cq.wcm.foundation.Image,
java.io.PrintWriter" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.util.Map" %>

<cq:includeClientLib js="mycomponent.imagear"/>

<%
try {
Resource res = null;

if (currentNode.hasProperty("imageReference")) {
res = resource;
}

if (res == null) {
%>
Configure Image
<%
} else {
PropertyIterator itr = currentNode.getProperties();
Property prop = null; String text = "";
Map<String, String> aMap = new HashMap<String, String>();

while(itr.hasNext()){
prop = itr.nextProperty();

if(prop.getName().endsWith("AspectRatio")){
text = prop.getName();

if(currentNode.hasProperty(prop.getName() + "Text")){
text = currentNode.getProperty(prop.getName() + "Text").getString();
}

aMap.put(prop.getName(), text);
}
}

Image img = null; String src = null;

if(aMap.isEmpty()){
%>
Cropped Images with custom aspect ratios not available
<%
}else{
for(Map.Entry entry : aMap.entrySet()){
img = new Image(res);
img.setItemName(Image.PN_REFERENCE, "imageReference");
img.setSuffix(entry.getKey() + ".jpg");
img.setSelector("img");

src = img.getSrc();
%>
<br><br><b><%=entry.getValue()%></b><br><br>
<img src='<%=src%>'/>
<%
}
}
}
} catch (Exception e) {
e.printStackTrace(new PrintWriter(out));
}
%>

8) Add the following code to /apps/imagecustomar/image/img.GET.java. This java logic reads crop co-ordinates from CRX and outputs the image bytes to browser

package apps.imagecustomar.image;

import java.awt.*;
import java.io.IOException;
import java.io.InputStream;

import javax.jcr.RepositoryException;
import javax.jcr.Property;
import javax.servlet.http.HttpServletResponse;

import com.day.cq.commons.ImageHelper;
import com.day.cq.wcm.foundation.Image;
import com.day.cq.wcm.commons.AbstractImageServlet;
import com.day.image.Layer;
import org.apache.commons.io.IOUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;

public class img_GET extends AbstractImageServlet {
protected Layer createLayer(ImageContext c) throws RepositoryException, IOException {
return null;
}

protected void writeLayer(SlingHttpServletRequest req, SlingHttpServletResponse resp, ImageContext c, Layer layer)
throws IOException, RepositoryException {
Image image = new Image(c.resource);
image.setItemName(Image.NN_FILE, "image");
image.setItemName(Image.PN_REFERENCE, "imageReference");

if (!image.hasContent()) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}

layer = image.getLayer(false, false,false);

String rUri = req.getRequestURI();
String ratio = rUri.substring(rUri.lastIndexOf("/") + 1, rUri.lastIndexOf(".jpg"));
String cords = c.properties.get(ratio, "");

boolean modified = false;

if(!"".equals(cords)){
Rectangle rect = ImageHelper.getCropRect(cords, c.resource.getPath());
layer.crop(rect);

modified = true;
}else{
modified = image.crop(layer) != null;
}

modified |= image.resize(layer) != null;
modified |= image.rotate(layer) != null;

if (modified) {
resp.setContentType(c.requestImageType);
layer.write(c.requestImageType, 1.0, resp.getOutputStream());
} else {
Property data = image.getData();
InputStream in = data.getStream();
resp.setContentLength((int) data.getLength());
String contentType = image.getMimeType();

if (contentType.equals("application/octet-stream")) {
contentType=c.requestImageType;
}

resp.setContentType(contentType);
IOUtils.copy(in, resp.getOutputStream());
in.close();
}

resp.flushBuffer();
}
}

9) Finally, add the following properties in your CRX node /apps/imagecustomar/image/dialog/items/img






Viewing all articles
Browse latest Browse all 525

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>