hidpi

Blurry Graphics in ADF Faces

Posted on Updated on

Ever since Apple introduced the Retina Display in 2010, modern devices have changed to display content at twice (@2x), three times (@3x), four times (@4x) [or even fractions in-between] the resolution that we have been used to in the past.

For app developers using bitmap graphics, this can be a bit of a nightmare to support. You have to generate graphics at multiple resolutions and change your application to reference different sizes of graphics. Unless you are using photographic content, you may find it easier to deal with SVG (vector graphic) images instead.

In ADF Faces applications, there are at least 3 categories of ways to display images:

  1. Image components
  2. Components that have an image somewhere inside of it (and that don’t offer a style attribute explicitly for that image)
  3. CSS background-image styles

blurry-images

SVG

For all 3 use cases, simply use the .svg file as you have been accustomed to using for .png files. Just make sure the graphic was well-designed to look good on an @1x display if legacy support is important to you.

PNG (or other bitmap graphic)

For use case 1, you specify your image in the “source” attribute then the dimensions in the “inlineStyle” attribute.

For use case 2, you specify your image in the appropriate image attribute, e.g. “icon” and specify a marker “styleClass” that you’ll need to make a style block definition for (e.g. via an af:resource tag). This block will use that marker then space then “IMG” for its selector and then in the body of the block, specify the width and height.

For use case 3, you are dealing with CSS styles (e.g. in an af:resource). Since browser support is not universal, you will need to specify 3 kinds of selectors: (a) the default fallback for legacy browsers–where you define your @1x graphic, (b) the WebKit @2x override, (c) the modern @2x override. You can make additional blocks for @3x, @4x, etc. (You may optionally use this technique to hide/show specific component instances for use cases 1 & 2 too if you really want your app to display resolution-specific graphics.)

Sample code

<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
  xmlns:trh="http://myfaces.apache.org/trinidad/html">
  <jsp:directive.page contentType="text/html;charset=UTF-8"/>
  <f:view>
    <af:document id="d1" title="Blurry Graphics" inlineStyle="padding:10px;">
      <f:facet name="metaContainer">
        <af:resource type="css">
          /* Dimensions for bitmap graphics on components that don't have
             style attributes for internal image elements. */
          .MyIconHolder IMG {
            width: 24px;
            height: 24px;
          }
          /* Example of background-image graphics */
          .MyBackgroundPNGImage {
            background-repeat: no-repeat;
            width: 24px;
            height: 24px;
            background-image: url(images/image@1x.png);
          }
          @media only screen and (-webkit-min-device-pixel-ratio: 2) {
            .MyBackgroundPNGImage {
              background-image: url('images/image@2x.png');
              -webkit-background-size: 24px 24px;
            }
          }
          @media only screen and (min-resolution: 2dppx) {
            .MyBackgroundPNGImage {
              background-image: url('images/image@2x.png');
              background-size: 24px 24px;
            }
          }
          .MyBackgroundSVGImage {
            background-repeat: no-repeat;
            width: 24px;
            height: 24px;
            background-image: url(images/image.svg);
          }
        </af:resource>
      </f:facet>
      <af:form id="f1">
        <trh:tableLayout id="tl1" borderWidth="1">
          <trh:rowLayout id="rl1">
            <trh:cellFormat id="cf1" header="true">
              <af:outputText id="ot1" value="Use Case"/>
            </trh:cellFormat>
            <trh:cellFormat id="cf2" header="true">
              <af:outputText id="ot2" value="@1x PNG"/>
            </trh:cellFormat>
            <trh:cellFormat id="cf3" header="true">
              <af:outputText id="ot3" value="@2x PNG"/>
            </trh:cellFormat>
            <trh:cellFormat id="cf4" header="true">
              <af:outputText id="ot4" value="SVG"/>
            </trh:cellFormat>
          </trh:rowLayout>
          <trh:rowLayout id="rl2">
            <trh:cellFormat id="cf5" halign="center">
              <af:outputText id="ot5" value="af:image"/>
            </trh:cellFormat>
            <trh:cellFormat id="cf6" halign="center">
              <af:image id="i1" source="/images/image@1x.png"/>
            </trh:cellFormat>
            <trh:cellFormat id="cf7" halign="center">
              <af:image id="i2" source="/images/image@2x.png"
                inlineStyle="width:24px;height:24px;"/>
            </trh:cellFormat>
            <trh:cellFormat id="cf8" halign="center">
              <af:image id="i3" source="/images/image.svg"/>
            </trh:cellFormat>
          </trh:rowLayout>
          <trh:rowLayout id="rl3">
            <trh:cellFormat id="cf9" halign="center">
              <af:panelGroupLayout id="pgl1" layout="vertical">
                <af:outputText id="ot6" value="af:commandToolbarButton"/>
                <af:outputText id="ot7" value="(no style attribute for dimensions)"/>
              </af:panelGroupLayout>
            </trh:cellFormat>
            <trh:cellFormat id="cf10" halign="center">
              <af:commandToolbarButton id="ctb1" icon="/images/image@1x.png"/>
            </trh:cellFormat>
            <trh:cellFormat id="cf11" halign="center">
              <af:commandToolbarButton id="ctb2" icon="/images/image@2x.png"
                styleClass="MyIconHolder"/>
            </trh:cellFormat>
            <trh:cellFormat id="cf12" halign="center">
              <af:commandToolbarButton id="ctb3" icon="/images/image.svg"/>
            </trh:cellFormat>
          </trh:rowLayout>
          <trh:rowLayout id="rl4">
            <trh:cellFormat id="cf13" halign="center">
              <af:panelGroupLayout id="pgl2" layout="vertical">
                <af:outputText id="ot8" value="af:panelGroupLayout"/>
                <af:outputText id="ot9" value="(background-image styling)"/>
              </af:panelGroupLayout>
            </trh:cellFormat>
            <trh:cellFormat id="cf14" halign="center" columnSpan="2">
              <af:panelGroupLayout id="pgl3" layout="vertical"
                styleClass="MyBackgroundPNGImage"/>
            </trh:cellFormat>
            <trh:cellFormat id="cf16" halign="center">
              <af:panelGroupLayout id="pgl4" layout="vertical"
                styleClass="MyBackgroundSVGImage"/>
            </trh:cellFormat>
          </trh:rowLayout>
        </trh:tableLayout>
      </af:form>
    </af:document>
  </f:view>
</jsp:root>
Advertisements

HiDPI Images in MAF or ADF Mobile Applications

Posted on Updated on

Whether you are developing a MAF (or ADF Mobile) application or a classic ADF Faces application, it is becoming more and more important to support HiDPI screens (high resolution or “retina” displays). There is no easier way to make your application look out-dated than to use grainy, unprofessional image assets or use them improperly.

In HTML, the “px” unit (sometimes referred to as a “dp” or density-independent pixel) corresponds to the same amount of space regardless of the DPI of your screen (however, the number of screen pixels may vary). The original iPhone models did not have HiDPI displays. Each point of color on those screens corresponds to one HTML CSS “px” unit. Newer iPhone models introduced a HiDPI (or “retina”) display that has 4 screen pixels in the same amount of space that 1 screen pixel used to take up (2 screen pixels wide by 2 screen pixels tall); on these new screens, the width and height “px” values use twice the amount of screen pixels.

Why is this a common problem? Since MAF (or ADF Mobile) uses HTML, displaying an image is not as simple as just specifying the source path and magically hoping the browser will know that you are using a high resolution image. You must specify dimensions to go along with the source path.

Let’s work with an example. You have an image named “image-64.png”. This image has a size of 64 by 64 individual dots of color (individual points of color information). If you coded your page like the following, the image will be shown with a width of 64px and a height of 64px (one color dot per “px”):

<amx:image id="i1" source="images/image-64.png"/>

This would look just fine on a classic low-DPI display. However, on a HiDPI display, it still takes up the same space but since there are more screen pixels, the image will look very grainy.

In order to look crisp and professional, you need to set a size so that each dot of color corresponds to at least one screen pixel. For a HiDPI display, this means your image needs a width and a height specified such that you use 2 dots of image color information per HTML CSS “px” unit (e.g. a 64 by 64 sized image should be specified to use a width of 32px and a height of 32px. In code, your page should look like this:

<amx:image id="i1" inlineStyle="width:32px;height:32px" source="images/image-64.png"/>

Even if you still want to support legacy screens for your application, this same image (with the same specified width and height) will look beautiful on low-DPI screens because of how images are processed modern browsers.

If for some reason you really needed or wanted to specify alternate images for each kind of screen, you have the option to use a screen properties EL variable to toggle the rendered state of alternate amx:image components or simply use that EL to alter the inlineStyle and the source path as desired.

PNG vs. SVG?

Consider whether using PNG is really the right choice to begin with. SVG (scalable vector graphics) files tend to be smaller file sizes when created without excessive anchor points, no embedded bitmap data, reduced decimal number precision, excess whitespace removed, and comments removed. If the resolution of your display gets better, the SVG will look that much better; you don’t need multiple resolution versions of the same image resources. If the SVG was given the proper width/height to begin with by its creator, you won’t even need to muck with inlineStyles since the image will naturally be displayed at the proper size.