A graphical counter on GAEJ (Google App Engine for Java) using Images API Service
>> 26 May 2010
Images Service on GAEJ provides the ability to manipulate images, thus you can composite multiple images into a single one. I'll use this possibility to display a graphical hit counter. This tutorial is only a kind of how-to. I'm sure you can write real programs using the instructions given in this post. For simplicity reasons error handling are reduced to the minimum.
The idea is pretty simple, persist a counter using Memcache or DataStore, have digits from 0 to 9 as PNG images, read images as bytes, make images and composites of these images, put all composites in a List and finally use this List to get the composed image.
PNG or Gif Images?
Png images does not render transparency very well ! If you want to get clean, with transparent background counter, you can replace Png by .gif images, and setContentType accordingly.
Lets go !
First declare some variables
List<Composite> listComposites=new ArrayList<Composite>();
Image img=null;
Composite composite=null;
//n is the value of the counter
int n=0;
//offset between images
int offset=0;
Create a background image
This one will normally has the total of all images width as it's width. All images have the same height.
See readImage method below.
//create a background image, this image (a physical one) is read on disk as bytes
//com.google.appengine.api.images.Image;
img=ImagesServiceFactory.makeImage(readImage("images/counter/b.png"));
makeComposite
makeComposite is defined like this :
public static Composite makeComposite(Imageimage,
intxOffset,
intyOffset,
floatopacity,
Composite.Anchoranchor)
Parameters:
image - The image to be composited.
xOffset - Offset in the x axis from the anchor point.
yOffset - Offset in the y axis from the anchor point.
opacity - Opacity to be used for the image in range [0.0, 1.0].
anchor - Anchor position from the enum Composite.Anchor. The anchor position of the image is aligned with the anchor position of the canvas and then the offsets are applied.
Make a Composite of the background Image
//create a Composite for the background image
composite= ImagesServiceFactory.makeComposite(img, 0, 0, 1, Anchor.TOP_LEFT);
Add the background Composite to the List
listComposites.add(composite);
Get the value of the counter
See getNumber() method below.
//get the number you want to display
n=getNumber();
Get individual numbers of the counter
Using a String and it's characters (not very elegant, but it works)
String s=Integer.toString(n);
for(char c:s.toCharArray())
Make Images and Composites
- Make an image for every number of the counter,
- Make a Composite,
- Add the Composite to the List of Composites
- Add the width of an image to the offset
for(char c:s.toCharArray())
{
img=ImagesServiceFactory.makeImage(readImage("images/counter/"+c+".png"));
composite=ImagesServiceFactory.makeComposite(img, offset, 0, 1, Anchor.TOP_LEFT);
listComposites.add(composite);
offset+=14;
}
Define a color for the background
color - Background color of the canvas in ARGB format.
long color=0xffffff;
Get the final image
Use composite method of ImageService
This method return an Image, it's bytes can be get by using getImageData(), you can write these bytes to your servlet (for instance ) to display the image.
Image composite(java.util.Collection<Composite>composites,
intwidth,
intheight,
longcolor)
Applies the provided Collection of Composites using a canvas with dimensions determined by width and height and background color color. Uses PNG as its output encoding.
Parameters:
composites - Compositing operations to be applied.
width - Width of the canvas in pixels.
height - Height of the canvas in pixels.
color - Background color of the canvas in ARGB format.
Returns:
A new image containing the result of composition.
Throws:
java.lang.IllegalArgumentException - If width or height is greater than 4000, color is outside the range [0, 0xffffffff], composites contains more than 16 elements or something is wrong with the contents of composites.
ImagesServiceFailureException - If there is a problem with the Images Service
TutorCounterServlet.java
package com.java_javafx;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.jdo.PersistenceManager;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.appengine.api.images.Composite;
import com.google.appengine.api.images.Image;
import com.google.appengine.api.images.ImagesServiceFactory;
import com.google.appengine.api.images.Composite.Anchor;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheServiceException;
import com.google.appengine.api.memcache.MemcacheServiceFactory;
/**
* @author Kaesar ALNIJRES
*
*/
public class TutorCounterServlet extends HttpServlet{
private PersistenceManager pm=null;
private final static Logger logger=Logger.getLogger("CounterServlet");
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException,IOException {
resp.setContentType("image/png");
ServletOutputStream out = resp.getOutputStream();
//First some variables
List<Composite> listComposites=new ArrayList<Composite>();
Image img=null;
Composite composite=null;
int n=0;//This one will be rendered by a composition of image
int offset=0;//offset between images (normally width of one image)
//create a background image, this image (a physical one) is read on disk as bytes
//com.google.appengine.api.images.Image;
img=ImagesServiceFactory.makeImage(readImage("images/counter/b.png"));
//create a Composite for the background image
composite= ImagesServiceFactory.makeComposite(img, 0, 0, 1, Anchor.TOP_LEFT);
//Add the background Composite to the list
listComposites.add(composite);
//get the number you want to display (your counter)
n=getNumber();
String s=Integer.toString(n);
//every character here is an individual number of the counter
for(char c:s.toCharArray())
{
//create an image from bytes for every number of the counter, make a composite for every image
//and add this compoiste to the list of composite
img=ImagesServiceFactory.makeImage(readImage("images/counter/"+c+".png"));
composite=ImagesServiceFactory.makeComposite(img, offset, 0, 1, Anchor.TOP_LEFT);
listComposites.add(composite);
//Modify this to the width of one image
offset+=14;
}
long color=0xffffff;
int widthOfCounter=70;
int heightOfCounter=16;
byte[] imgComplet=ImagesServiceFactory.getImagesService().composite(listComposites, widthOfCounter, heightOfCounter, color).getImageData();
out.write(imgComplet);
out.flush();
}
private byte[] readImage(String fileName)
{
byte[] buffer;
try
{
FileInputStream in=new FileInputStream(fileName);
buffer=new byte[in.available()];
in.read(buffer);
in.close();
return buffer;
}
catch(Exception e)
{
logger.severe("Exception "+e.getMessage());
return null;
}
}
//==============================================================================
//This method will read the counter value from memcache or DataStore. The counter is called "counter" in memcache
//give it any name you want
private int getNumber()
{
int counter=0;
try {
pm=PMF.get().getPersistenceManager();
//get memcache
MemcacheService memcache = MemcacheServiceFactory.getMemcacheService();
//increment counter
Long lCounter=memcache.increment("counter", 1);
//if the key is in memcache and increment is ok
if(lCounter !=null)
counter=lCounter.intValue();
else
{
List<Counter> pCounters = (List<Counter>) pm.newQuery("select from "+Counter.class.getName()).execute();
if (pCounters.isEmpty()) //if no record
{
counter=1;
memcache.put("counter",1);
}
else
{
counter=pCounters.size()+1;
memcache.put("counter",pCounters.size()+1);
}
}
Counter c1=new Counter();
c1.setCounter(1);
pm.makePersistent(c1);
}
catch (MemcacheServiceException e) {
// Ignore cache problems, nothing we can do. Put log a message
logger.severe("memcache exception "+e.getMessage());
}
catch(Exception e)
{
logger.severe("General exception "+e.getMessage());
}
finally
{
if(pm != null)
pm.close();
}
return counter;
}
//==============================================================================
}
Counter.java
package com.java_javafx;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import com.google.appengine.api.datastore.Key;
/**
* @author Kaesar ALNIJRES
*
*/
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Counter {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
@Persistent
private int counter;
/**
* @return the key
*/
public Key getKey() {
return key;
}
/**
* @param key the key to set
*/
public void setKey(Key key) {
this.key = key;
}
/**
* @return the counter
*/
public int getCounter() {
return counter;
}
/**
* @param counter the counter to set
*/
public void setCounter(int counter) {
this.counter = counter;
}
}
PFM.java
package com.java_javafx;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;
/**
* @author Kaesar ALNIJRES
*
*/
public final class PMF {
private static final PersistenceManagerFactory pmfInstance =
JDOHelper.getPersistenceManagerFactory("transactions-optional");
private PMF() {}
public static PersistenceManagerFactory get() {
return pmfInstance;
}
}
Thank you for reading. Hope you find this post helpful.
1 comments:
Thank you! I have been searching for an example of Composite() on the appengine
Post a Comment