For certain applications its useful to have an image template that can be stamped with contextual data when displayed to the end user.

This example comes from an online travel survey is being conducted where there is a specific for which the trips will be collected.

So to better inform the respondent of the window of time we are collecting trips for we generate an image contextualized with the specific date details that apply.

For example:

The red circled date information is what is stamped into the image.

The final image is constructed in several steps:

  1. Create new image.
  2. Write transparent background with black text onto the image.
  3. Convert surveyedDate into text and write onto the image.
  4. Fill the background to white.

Accessing the generated image can be done in two ways:

  1. #IRequestTargetUrlCodingStrategy which is mounted and returns the contextual image for a fixed path.
  2. #Resource which wraps the image creation logic but where the URL is auto-generated by wicket.

Create custom IRequestTargetUrlCodingStrategy and bind it to the /surveyed-date.png Image

Overview:

  1. mount the custom IRequestTargetUrlCodingStrategy in application.init();
    1. My custom strategy is hardcoded with the name of the image that it responds to: /surveyed-date.png
  2. When ever a request is sent to that image the strategy will either returned a cached copy or generate a new image by stamping the current date onto a template image.
    1. The page that places the image will have an image link with the
      <img src="/surveyed-date.png" />
      HTML tag.

First we create the IRequestTargetUrlCodingStrategy that will respond to requests to /surveyed-date.png

public class SessionScopedSurveyTimeImageRequestTarget implements
        IRequestTargetUrlCodingStrategy {

    private static final Logger    log    = Logger
                                            .getLogger(SessionScopedSurveyTimeImageRequestTarget.class);

// name of the image resource that we respond to
    private static final String    PATH    = "surveyed-date.png";
   
// service that generates the final image for the current state.
    private final TripTimeImageService    tripTimeImageService;

// spring session bean proxy that contains the current survey sample
    private final DataModelService    dmService;
   

    /**
     * @param tripTimeImageService
     *
     */
    public SessionScopedSurveyTimeImageRequestTarget(TripTimeImageService tripTimeImageService, DataModelService dmService) {
        this.tripTimeImageService = tripTimeImageService;
        this.dmService = dmService;

    }

  
    @Override
    public IRequestTarget decode(RequestParameters requestParameters) {

        // default to yesterday
        DateTime surveyedDate = new DateTime().minusDays(1);
       
        if (dmService.isCreated()) {
             // The surveyed date is set from the sample context.
            surveyedDate = new DateTime ( dmService.getDataModel().getSample().getSurveyedDate());
        }
        Resource r;
        try {
            // this spring service generates the Image Resource stamped with 'surveyedDate' detail
            r = tripTimeImageService.getImageResource(surveyedDate, true);
        }
        catch (IOException e) {
            log.error("decode(): exception", e);
            return null;
        }
       
        return new ResourceStreamRequestTarget (r.getResourceStream());
    }

   
    @Override
    public CharSequence encode(IRequestTarget requestTarget) {

        // intentionally does nothing
        return null;
    }

   
    @Override
    public String getMountPath() {

        return PATH;
    }

  
    @Override
    public boolean matches(IRequestTarget requestTarget) {

        return false;
    }

    @Override
    public boolean matches(String path, boolean caseSensitive) {

        // handle the match detection, i.e. path.equals ("surveyed-date.png")
        if (caseSensitive) {
       
            if (PATH.equals(path))
                return true;
            else
                return false;
        }
        else {

            if (PATH.toLowerCase().equals(path.toLowerCase()))
                return true;
            else
                return false;
        }
    }
}

Next we mount the strategy in Application.init(), as:

    mount (new SessionScopedSurveyTimeImageRequestTarget(tripTimeService, dmService));

The tripTimeService.getImageResource(surveyedDate, true) method is what does the work of converting the date into a particular format and then writing the text onto an image template.

How to create an image stamped with user specific context

public class TripTimeImageServiceImpl implements TripTimeImageService {

	private static final Logger										log				= Logger
																							.getLogger(TripTimeImageServiceImpl.class);


        // spring resource that points at the template image 
	private Resource												baseTripTimeImageFile;

	private DateTimeFormatter										dayOfWeekFormatter;

	private DateTimeFormatter										dateFormatter;

// Font to use to write the string version of the date onto the image template
	private static Font												font			= new Font(
																							Font.SANS_SERIF,
																							Font.BOLD,
																							14);
 
        // cache containing the created image for each 
	private Map<String, BufferedDynamicImageResource>	dateToImageMap	= new LinkedHashMap<String, BufferedDynamicImageResource>();

	private BufferedDynamicImageResource generateImage(DateTime surveyedDate,
			SurveyTime st, boolean cacheable) throws IOException {

		BufferedDynamicImageResource generatedImage = new BufferedDynamicImageResource(
				"png");

		
		String dow = dayOfWeekFormatter.print(surveyedDate);

		String date = dateFormatter.print(surveyedDate);

		DateTime nextDay = surveyedDate.plusDays(1);

		String nextDow = dayOfWeekFormatter.print(nextDay);
		String nextDate = dateFormatter.print(nextDay);

		BufferedImage overlayImage = ImageIO.read(baseTripTimeImageFile.getFile());

		int width = overlayImage.getWidth();

		int height = overlayImage.getHeight();

		
		
		Graphics2D overlayG2d = overlayImage.createGraphics();

		overlayG2d.setFont(font);
		

		overlayG2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		overlayG2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
				RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

	overlayG2d.setPaint(Color.BLACK);
		
		/*
		 * These offsets were calculated using a test case to repeatably place the details
		 * and with the Gimp to locate viable insertion points.
		 */
		// day of week
		overlayG2d.drawString(dow, 32, 90);

		// date
		overlayG2d.drawString(date, 32, 107);

		// next day of week
		overlayG2d.drawString(nextDow, 332, 90);

		// next date
		overlayG2d.drawString(nextDate, 332, 107);				
		
		BufferedImage target = new BufferedImage (width, height, BufferedImage.TYPE_INT_ARGB);
		
		Graphics2D targetG2d = target.createGraphics();
		
		targetG2d.setFont(font);
		

		targetG2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		targetG2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
				RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		
		targetG2d.setPaint(Color.white);
		
		targetG2d.fillRect(0, 0, width, height);
		
		targetG2d.setPaint(Color.RED);
		
		// fill with the background color (white)
//		targetG2d.fillRect(0, 0, width, height);
				
// draw the overlay (the printed surveyedDate detail) onto the target.	
		targetG2d.drawImage(overlayImage, 0, 0, null);

	
// do the final setup on the BufferedDynamicImageResource that will contain this image
		// note that the generated image does not change the base image.
		generatedImage.setImage(target);

		generatedImage.setCacheable(cacheable);
		generatedImage.setFormat("png");
		
		log.info("generated image");
		

		return generatedImage;
		
	}

	
	public void setBaseTripTimeImageFile(Resource baseTripTimeImageFile) {

		this.baseTripTimeImageFile = baseTripTimeImageFile;
	}

	
	public TripTimeImageServiceImpl() {

		// e.g Wednesday
		dayOfWeekFormatter = DateTimeFormat.forPattern("EEEE");

		// e.g. February 18, 2009
		dateFormatter = DateTimeFormat.forPattern("MMMM dd, yyyy");
	}

	
	@Override
	public org.apache.wicket.Resource getImageResource(DateTime surveyedDate, boolean cacheable)
			throws IOException {

		return getImageResource(surveyedDate, null, cacheable);

	}

	
	@Override
	public org.apache.wicket.Resource getImageResource(DateTime surveyedDate,
			SurveyTime previousDepartureTime, boolean cacheable) throws IOException {

			BufferedDynamicImageResource cachedImage = null;

			
			String cachedImageKey = TimeUtils.getInstance().getString(
					surveyedDate);


			cachedImage = this.dateToImageMap.get(cachedImageKey);
			
			
			if (cachedImage == null) {
				
				cachedImage = generateImage(surveyedDate, previousDepartureTime, cacheable);

				this.dateToImageMap.put(cachedImageKey, cachedImage);

			}
			
				return cachedImage;
		

	}

}

Using Custom Resource to stamp the image.

Overview:

The resource is aware of the current state and will adjust the image based upon it.

Notice how the contents of the getResourceStream(...) method are essentially the same as the IRequestTargetUrlCodingStrategy.decode (...) method from before.

public class SessionScopedSurveyedDateImageResource extends Resource {

	private static final Logger	log	= Logger
											.getLogger(SessionScopedSurveyedDateImageResource.class);

// spring session scoped bean providing the current context
	private final DataModelService	dmService;
	
// spring manged image stamping service	
	private final TripTimeImageService	tripTimeImageService;

// model containing the current question in the survey.
	private final IModel<QuestionLocator>	questionLocatorModel;
	

	/**
	 * @param entityLocatorModel 
	 * 
	 */
	public SessionScopedSurveyedDateImageResource(TripTimeImageService tripTimeImageService, DataModelService dmService) {
		this.tripTimeImageService = tripTimeImageService;
		this.dmService = dmService;
		
	}

	
	/* (non-Javadoc)
	 * @see org.apache.wicket.Resource#getResourceStream()
	 */
	@Override
	public IResourceStream getResourceStream() {

		// default to yesterday
        DateTime surveyedDate = new DateTime().minusDays(1);
       
        if (dmService.isCreated()) {
             // The surveyed date is set from the sample context.
            surveyedDate = new DateTime ( dmService.getDataModel().getSample().getSurveyedDate());
        }
        Resource r;
        try {
            // this spring service generates the Image Resource stamped with 'surveyedDate' detail
            r = tripTimeImageService.getImageResource(surveyedDate, true);
        }
        catch (IOException e) {
            log.error("decode(): exception", e);
            return null;
        }
       
        return r.getResourceStream();
		
	}
}

HTML:

<img wicket:id="surveyedDate" />

Java:

add (new Image ("surveyedDate", new SessionScopedSurveyedDateImageResource(tripTimeImageService, dmService));