How to Stamp an Image Template with Context Specific Details
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 we generate an image containing the window of time we are interested in and contextualize it with the specific date details that apply.
The final image is constructed in several steps:
- create new image.
- write transparent background with black text onto the image
- convert surveyedDate into text and render onto the image
- set the background to white
The generated image will be accessed through a hardcoded mount point.
Wicket Setup:
- mount the custom IRequestTargetUrlCodingStrategy in application.init();
- My custom strategy is hardcoded with the name of the image that it responds to: /surveyed-date.png
- 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.
- The page that places the image will have an image link with the src="/surveyed-date.png"
Create custom IRequestTargetUrlCodingStrategy and bind it to the /surveyed-date.png Image
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); // next we write the progress bar onto its own image so thay we can layer them and have the bar at the back of the image stack. 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; } }
Linking to the dynamic image from your pages
<img src="/surveyed-date.png" alt="Contextualized Image">