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:
- Create new image.
- Write transparent background with black text onto the image.
- Convert surveyedDate into text and write onto the image.
- Fill the background to white.
Accessing the generated image can be done in two ways:
- #IRequestTargetUrlCodingStrategy which is mounted and returns the contextual image for a fixed path.
- #Resource which wraps the image creation logic but where the URL is auto-generated by wicket.
Anchor | ||||
---|---|---|---|---|
|
Create custom IRequestTargetUrlCodingStrategy and bind it to the /surveyed-date.png Image
Overview:
- 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
HTML tag.Code Block <img src="/surveyed-date.png" />
- The page that places the image will have an image link with the
First we create the IRequestTargetUrlCodingStrategy that will respond to requests to /surveyed-date.png
Code Block |
---|
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:
Code Block |
---|
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.
Anchor | ||||
---|---|---|---|---|
|
How to create an image stamped with user specific context
Code Block |
---|
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; } } |
Anchor | ||||
---|---|---|---|---|
|
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.
Code Block |
---|
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:
Code Block |
---|
<img wicket:id="surveyedDate" /> |
Java:
Code Block |
---|
add (new Image ("surveyedDate", new SessionScopedSurveyedDateImageResource(tripTimeImageService, dmService)); |