-
Available since OmniFaces 2.0
The <o:graphicImage>
is a component that extends the standard <h:graphicImage>
with support for referencing an InputStream
or byte[]
property in the value
attribute, optionally as a data URI.
Data URI
Set dataURI
attribute to true
in order to render image in data URI format.
<o:graphicImage name="icon.png" dataURI="true" /> <!-- Faces resource as data URI -->
<o:graphicImage value="#{bean.icon}" dataURI="true" /> <!-- byte[]/InputStream property as data URI -->
This basically renders the image inline in HTML output immediately during Faces render response phase. This approach is very useful for a "preview" feature of uploaded images and works also in combination with view scoped beans. This approach is however not recommended for "permanent" and/or "large" images as it doesn't offer the browser any opportunity to cache the images for reuse, ~10KB would typically be the max even less so if there are more such images on the same page.
Image streaming
When not rendered as data URI, the InputStream
or byte[]
property must point to a stateless @
GraphicImageBean
or @Named @ApplicationScoped
bean. The property will namely be evaluated at the moment the browser requests the image content based on the URL as specified in HTML <img src>
, which is usually a different request than the one which rendered the Faces page. E.g.
@Named
@RequestScoped
public class Bean {
private List<Image> images; // Image class should NOT have "content" property, or at least it be lazy loaded.
@Inject
private ImageService service;
@PostConstruct
public void init() {
images = service.list();
}
public List<Image> getImages() {
return images;
}
}
@GraphicImageBean
public class Images {
@Inject
private ImageService service;
public byte[] get(Long id) {
return service.getContent(id);
}
}
<ui:repeat value="#{bean.images}" var="image">
<o:graphicImage value="#{images.get(image.id)}" />
</ui:repeat>
A @RequestScoped
and @SessionScoped
bean would theoretically work, but this is wrong design (a servlet is inherently also application scoped and stateless, not without reason). A @ViewScoped
wouldn't work because the image request doesn't share the Faces view state.
In case the property is a method expression taking arguments, each of those arguments will be converted to a string HTTP request parameter and back to actual objects using the converters registered by class as available via Application.createConverter(Class)
. So, most of standard types like Long
are already implicitly supported. In case you need to supply a custom object as argument for some reason, you need to explicitly register a converter for it yourself via @FacesConverter(forClass)
.
Caching
In case your "image" entity supports it, you can also supply the "last modified" property which will be used in the ETag
and Last-Modified
headers and in If-Modified-Since
checks, hereby improving browser caching. The lastModified
attribute supports both Date
and Long
as timestamp in milliseconds.
<ui:repeat value="#{bean.images}" var="image">
<o:graphicImage value="#{images.get(image.id)}" lastModified="#{image.lastModified}" />
</ui:repeat>
When unspecified, then the "default resource maximum age" as set in either the Mojarra specific context parameter com.sun.faces.defaultResourceMaxAge
or MyFaces specific context parameter org.apache.myfaces.RESOURCE_MAX_TIME_EXPIRES
will be used, else a default of 1 week will be assumed.
Image types
When rendered as data URI, the content type will be guessed based on content header. So far, WEBP, JPEG, PNG, GIF, ICO, SVG, BMP and TIFF are recognized. If the content header is unrecognized, or when the image is rendered as regular image source, then the content type will default to "image"
without any subtype. This should work for most images in most browsers. This may however fail on newer images or in older browsers. In that case, you can explicitly specify the image type via the type
attribute which must represent a valid file extension. E.g.
<o:graphicImage value="#{images.get(image.id)}" type="svg" />
The content type will be resolved via Faces.getMimeType(String)
. You can add unrecognized ones as <mime-mapping>
in web.xml
. E.g.
<mime-mapping>
<extension>svg</extension>
<mime-type>image/svg+xml</mime-type>
</mime-mapping>
SVG view modes
When serving a SVG image, you can use fragment
attribute to trigger SVG view modes (beware of browser support). E.g.
<o:graphicImage value="#{images.get(image.id)}" type="svg" fragment="svgView(viewBox(0,50,200,200))" />
Lazy loading
Since OmniFaces 3.10, you can set the lazy
attribute to true
to indicate that the referenced image should only be loaded when the window is finished loading and the image is visible in the viewport.
<o:graphicImage ... lazy="true" />
This attribute is ignored when the dataURI
attribute is set to true
.
Design notes
The bean class name and method name will end up in the image source URL. Although this is technically harmless and not tamperable by hackers, you might want to choose a "sensible" class and method name for this purpose.
Like <h:graphicImage>
, the value
attribute is ignored when the name
attribute is specified (for Faces resources). And, the value
attribute of <o:graphicImage>
does not support URLs anymore. For that, just keep using <h:graphicImage>
or even plain <img>
.
The below one renders Faces resource as data URI
The below one renders InputStream property as resource
The below one renders InputStream property as data URI
The below one renders byte[] property taking a Long argument as resource in <ui:repeat>
loop
The below one renders InputStream property with SVG content as resource
The below one renders the same image as a lazy image
<h3>The below one renders Faces resource as data URI</h3>
<p>
<o:graphicImage library="layout" name="img/OmniFaces-logo-90x90-black.png" dataURI="true" />
</p>
<h3>The below one renders InputStream property as resource</h3>
<p>
<o:graphicImage value="#{images.logo}" lastModified="#{startup.time}" />
</p>
<h3>The below one renders InputStream property as data URI</h3>
<p>
<o:graphicImage value="#{images.logo}" dataURI="true" />
</p>
<h3>The below one renders byte[] property taking a Long argument as resource in <code><ui:repeat></code> loop</h3>
<p>
<ui:repeat value="#{images.ids}" var="id">
<o:graphicImage value="#{images.getContent(id)}" lastModified="#{startup.time}" />
</ui:repeat>
</p>
<h3>The below one renders InputStream property with SVG content as resource</h3>
<p>
<o:graphicImage value="#{images.svgLogo}" type="svg" lastModified="#{startup.time}" height="90" />
</p>
<h3>The below one renders the same image as a lazy image</h3>
<p>
<o:graphicImage value="#{images.svgLogo}" type="svg" lastModified="0#{startup.time}" height="90" lazy="true" />
</p>
package org.omnifaces.showcase.components;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
import org.omnifaces.cdi.GraphicImageBean;
import org.omnifaces.util.Faces;
import org.omnifaces.util.Utils;
@GraphicImageBean
public class Images {
private static final Map<Long, String> IMAGES = Collections.unmodifiableMap(new TreeMap<Long, String>() { private static final long serialVersionUID = 1L; {
put(1L, "black");
put(2L, "blue");
put(3L, "yellow");
put(4L, "gray");
put(5L, "red");
put(6L, "green");
}});
public InputStream getLogo() {
// Note: this is a dummy example. In reality, you should be able to take e.g. a Long argument as ID and then
// return the desired byte[] content from some service class by given ID.
return Faces.getResourceAsStream("/resources/layout/img/OmniFaces-logo-90x90-black.png");
}
public byte[] getContent(Long id) throws IOException {
// Note: this is a dummy example. In reality, you should be able to return the desired byte[] content from some
// service class by given ID.
return Utils.toByteArray(Faces.getResourceAsStream("/resources/layout/img/OmniFaces-logo-90x90-" + IMAGES.get(id) + ".png"));
}
public Long[] getIds() {
// Note: this is just a dummy example. In reality, you should be able to obtain them from another request/view
// scoped bean as ID of an entity representing the image.
return IMAGES.keySet().toArray(new Long[IMAGES.size()]);
}
public InputStream getSvgLogo() {
// Note: this is a dummy example. In reality, you should be able to take e.g. a Long argument as ID and then
// return the desired byte[] content from some service class by given ID.
return Faces.getResourceAsStream("/resources/layout/img/OmniFaces-logo.svg");
}
}
VDL documentation
API documentation
Java source code
org.omnifaces.resourcehandler.DefaultResourceHandler
org.omnifaces.resourcehandler.GraphicResource
org.omnifaces.el.ExpressionInspector
org.omnifaces.resourcehandler.GraphicResourceHandler
org.omnifaces.el.MethodReference
org.omnifaces.resourcehandler.DynamicResource
org.omnifaces.cdi.GraphicImageBean
org.omnifaces.component.output.GraphicImage