- image format like "png" or "jpg"
- file name of base image
- file name of mask image
- color (in HEX without leading "#" or "0x" signs)
http://host:port/webappcontext/masked/png/flag/flag-mask/FF0000/mfile.imgdrawerTo save me pain for manually parsing of URL string and extraction of all parameters I have took Jersey - reference implementation for building RESTful Web services. Jersey's RESTful Web services are implemented as servlet which takes requests and does some actions according to URL structure. We just need to implement such actions. My action (and task) is image drawing / painting. That occurs by means of Java 2D API - a set of classes for advanced 2D graphics and imaging. Let me show the end result at first. Base image looks as follows
Mask image looks as follows
And now the magic is comming. I'm sending a GET request to draw a red flag (color FF0000) dynamically:
I'm sending a GET request to draw a green flag (color 00FF00):
Do you want to see a blue image? No problem, I can type as color 00ff.
You need four things to achieve that:
1. Dependencies to Jersey and Java 2D Graphics. Maven users can add these as follows:
<dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-server</artifactId> <version>... last version ...</version> </dependency> <dependency> <groupId>com.sun.media</groupId> <artifactId>jai_codec</artifactId> <version>... last version ...</version> </dependency> <dependency> <groupId>com.sun.media</groupId> <artifactId>jai_imageio</artifactId> <version>... last version ...</version> </dependency>2. Two image files in the web application. I have placed them under webapp/resources/themes/app/images/ and they are called in my case flag.png and flag-mask.png.
3. Configuration for Jersey servlet in web.xml. My configuration is
<servlet> <servlet-name>imagedrawer</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <init-param> <param-name>com.sun.jersey.config.property.packages</param-name> <param-value>ip.client.commons.web.servlet</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>imagedrawer</servlet-name> <url-pattern>*.imgdrawer</url-pattern> </servlet-mapping>Image-URL should be ended with imgdrawer to be able to handled by Jersey servlet. By the way, my FacesServlet is mapped to *.jsf and Struts ActionServlet to *.do. Init parameter com.sun.jersey.config.property.packages points to the Java package where the handler class is placed. Writing of handler class is the step 4.
4. Handler class which treats GET requests and draws / paints a new masked image by means of Java 2D API. The new image is a composition of two predefined images from the step 2. The mask gets painted and overlapped with the base image. The code is more or less documented, so that I omit any comments :-)
/** * Jersey annotated class for image drawing. Drawed images don't need to be cached server side because they are cached * proper on the client side. Set "expires" and "max-age" ensure client side caching. */ @Path("/") @Produces("image/*") @Singleton public class ImageDrawer { @Context ServletContext context; /** directory for image files (can be done configurable if needed) */ private String imageDir = "/resources/themes/app/images/"; /** * Gets composed image consist of base and mask image. * * @param format format, e.g. "png", "jpg". * @param basefile file name of base image without file extension * @param maskfile file name of mask image without file extension * @param hexcolor color in HEX without leading "#" or "0x" signs * @return Response new composed image * @throws WebApplicationException thrown exception, 404 or 500 status code. */ @GET @Path("/masked/{format}/{basefile}/{maskfile}/{hexcolor}/{img}") public Response getImage(@PathParam("format") String format, @PathParam("basefile") String basefile, @PathParam("maskfile") String maskfile, @PathParam("hexcolor") String hexcolor) { // check parameters if (format == null || basefile == null || maskfile == null || hexcolor == null) { throw new WebApplicationException(404); } // try to get images from web application InputStream is1 = context.getResourceAsStream(imageDir + basefile + "." + format); if (is1 == null) { throw new WebApplicationException(404); } InputStream is2 = context.getResourceAsStream(imageDir + maskfile + "." + format); if (is2 == null) { throw new WebApplicationException(404); } RenderedImage img1 = renderImage(is1); RenderedImage img2 = renderImage(is2); // convert HEX to RGB Color color = Color.decode("0x" + hexcolor); // draw the new image BufferedImage resImage = drawMaskedImage(img1, img2, color); byte[] resImageBytes = null; ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { // use Apache IO ImageIO.write(resImage, format, baos); baos.flush(); resImageBytes = baos.toByteArray(); } catch (IOException e) { throw new WebApplicationException(500); } finally { try { baos.close(); } catch (IOException e) { ; } } // cache in browser forever CacheControl cacheControl = new CacheControl(); cacheControl.setMaxAge(Integer.MAX_VALUE); return Response.ok(resImageBytes, "image/" + format).cacheControl(cacheControl) .expires(new Date(Long.MAX_VALUE)).build(); } // Utilities (written not by me, copyright ©) private RenderedImage renderImage(InputStream in) { SeekableStream stream = createSeekableStream(in); boolean isImageIOAvailable = false; if (Thread.currentThread().getContextClassLoader(). getResource("META-INF/services/javax.imageio.spi.ImageReaderSpi") != null) { isImageIOAvailable = true; } return JAI.create(isImageIOAvailable ? "ImageRead" : "stream", stream); } private BufferedImage drawMaskedImage(RenderedImage orig, RenderedImage mask, Color color) { BufferedImage overlay = manipulateImage(mask, null); return manipulateImage(orig, new ImageManipulator() { public void manipulate(final Graphics2D g2d) { float[] scaleFactors = new float[] { 2f * (color.getRed() / 255f), 2f * (color.getGreen() / 255f), 2f * (color.getBlue() / 255f), color.getAlpha() / 255f}; float[] offsets = new float[] {0, 0, 0, 0}; g2d.drawImage(overlay, new RescaleOp(scaleFactors, offsets, null), 0, 0); } }); } private BufferedImage manipulateImage(RenderedImage orig, ImageManipulator manipulator) { BufferedImage image; boolean drawOriginal = false; ColorModel colorModel = orig.getColorModel(); int colorSpaceType = colorModel.getColorSpace().getType(); if (colorModel.getPixelSize() >= 4 && colorSpaceType != ColorSpace.TYPE_GRAY) { image = new RenderedImageAdapter(orig).getAsBufferedImage(); } else if (colorSpaceType == ColorSpace.TYPE_GRAY) { image = new BufferedImage(orig.getWidth(), orig.getHeight(), BufferedImage.TYPE_INT_ARGB); drawOriginal = true; } else { image = new BufferedImage(orig.getWidth(), orig.getHeight(), BufferedImage.TYPE_BYTE_INDEXED); drawOriginal = true; } Graphics2D g2d = image.createGraphics(); if (drawOriginal) { g2d.drawImage(new RenderedImageAdapter(orig).getAsBufferedImage(), 0, 0, null); } if (manipulator != null) { manipulator.manipulate(g2d); } return image; } private interface ImageManipulator {void manipulate(Graphics2D g2d);} }
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.