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:
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:
<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:
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());
}
}
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.