AEM integration with webp
A question from my colleagues in regards to AEM’s support for webp started a research path. Since, I took more than 1 year pause from AEM, I started looking around for the possibility of serving also webp images, for all the images already in DAM.
As a first step I started ask the community on stackoverflow and Adobe Forum. The answer received was to use a webp java library that provides support for reading and writing webp images .
Note: You will notice I have usedcom.github.nintha library. At the end of the article I will explain the reasoning for it and how I actually ditched it to work.
The steps for resolving this were:
- To add the 3rd party jar to AEM
- Create a simple servlet that receives as queryparam the image path as and returns the webp image.
Adding the 3rd party jar
In AEM one can install via in Apache Felix only bundled jar, for the simple jars, one can embed them using the maven-bundle-plugin in tree steps:
- Add the library jar to your application pom.xml (the main one)
<dependency>
<groupId>com.github.nintha</groupId>
<artifactId>webp-imageio-core</artifactId>
<version>0.0.1</version>
</dependency>
2. Modify the pom.xml from the core app to add the dependency and embed the jar. The core/pom.xml will have something like:
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-scr-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Import-Package>javax.inject;version=0.0.0,*</Import-Package>
<Export-Package>andra.core.*</Export-Package>
<Sling-Model-Packages>
andra.core
</Sling-Model-Packages>
<Embed-Dependency>webp-imageio-core</Embed-Dependency>
<Embed-Transitive>true</Embed-Transitive>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
....
<dependency>
<groupId>com.github.nintha</groupId>
<artifactId>webp-imageio-core</artifactId>
</dependency>
Things to mention: the Embed-Dependency tag needs to be populated with the artifactId. The <Embed-Transitive> tag specifies to embed also the dependencies of the embedded jar. (eg. the webp-imageio-core has as dependency org.scijava – native-lib-loader, which will be embedded as well)
In order to test that your 3rd party was successfully embedded, one can take a look at the bundle information:
Create the Servlet to transform to webp
For the purposes of this POC, it will be only a basic Servlet, it can be enhanced, of course to work with image renditions, or to work receive the path as a suffix – which would be the right way as we want the images to be cached.
package andra.core.servlets;
import com.day.cq.dam.api.Asset;
import com.luciad.imageio.webp.WebPWriteParam;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.sling.SlingServlet;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.osgi.framework.Constants;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.stream.MemoryCacheImageOutputStream;
import javax.servlet.ServletException;
import java.awt.image.BufferedImage;
import java.io.IOException;
@SlingServlet(paths = "/bin/webpTransformer",
methods = HttpConstants.METHOD_GET
)
@Properties({
@Property(name = Constants.SERVICE_DESCRIPTION, value = "WebP Transformer Servlet")
})
public class WepServlet extends SlingSafeMethodsServlet {
public static final String IMAGE_WEBP = "image/webp";
@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
response.setContentType(IMAGE_WEBP);
BufferedImage image = getImage(request);
// Obtain a WebP ImageWriter instance
ImageWriter writer = ImageIO.getImageWritersByMIMEType(IMAGE_WEBP).next();
// Configure encoding parameters
WebPWriteParam writeParam = new WebPWriteParam(writer.getLocale());
writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
writeParam.setCompressionType("Lossless");
ImageOutputStream outputStream = new MemoryCacheImageOutputStream(
response.getOutputStream());
// Configure the output on the ImageWriter
writer.setOutput(outputStream);
// Encode
writer.write(null, new IIOImage(image, null, null), writeParam);
outputStream.flush();
outputStream.close();
}
private BufferedImage getImage(SlingHttpServletRequest request) throws IOException {
ResourceResolver resourceResolver = request.getResourceResolver();
String path = request.getParameter("path");
Resource res = resourceResolver.getResource(path);
Asset asset = res.adaptTo(Asset.class);
return ImageIO.read(asset.getOriginal().getStream());
}
}
Why and how nintha ?
A question I would ask is why use nintha over luciad, as in the end nintha is just a luciad wrapper. Of course, I always try to use the standard lib, where possible, however when embedding luciad compiled on my machine I would get the following error:
Could not initialize class com.luciad.imageio.webp.WebPEncoderOptions
Cannot serve request to /bin/webpTransformer in andra.core.servlets.WepServlet
Exception:
java.lang.NoClassDefFoundError: Could not initialize class com.luciad.imageio.webp.WebPEncoderOptions
at com.luciad.imageio.webp.WebPWriteParam.(WebPWriteParam.java:30)
at com.luciad.imageio.webp.WebPWriter.getDefaultWriteParam(WebPWriter.java:38)
After a couple of days, I decided to give nintha a go. However, it did not go as smooth as I wanted, as know I would receive:
org.scijava.nativelib.NativeLibraryUtil Problem with library
java.lang.UnsatisfiedLinkError:
Meaning, that the native library included in nintha was not compatible with the OS the app was running. So, I had to add compile the native app on my local machine, add it manually to the nintha library and recomile. This is not a solution I like as it would be a downside when migration to another server – the library needs to encapsulate the native app for the specific environment.