View Javadoc

1   package org.jmage.filter.merge;
2   
3   import org.apache.log4j.Logger;
4   import org.apache.log4j.Priority;
5   import org.jmage.ApplicationContext;
6   import org.jmage.filter.ConfigurableImageFilter;
7   import org.jmage.filter.FilterException;
8   import org.jmage.resource.ResourceManager;
9   import org.jmage.util.ColorUtil;
10  
11  import javax.media.jai.JAI;
12  import javax.media.jai.PlanarImage;
13  import java.awt.*;
14  import java.awt.font.FontRenderContext;
15  import java.awt.font.TextLayout;
16  import java.awt.geom.Rectangle2D;
17  import java.awt.image.BufferedImage;
18  import java.net.URI;
19  import java.util.HashMap;
20  import java.util.Properties;
21  
22  /***
23   * Overlays the image with a String. Specify the following imageProperties to change
24   * the appearance:<p>
25   * <p/>
26   * <b>TEXT</b>: the string to overlay the image with.<br>
27   * <b>COLOR</b>: [RRGGBB] hex color code, i.e. "000000" for black (the default).<br>
28   * <b>OPACITY</b>: [0-100] opacity percentage of the color, where 0 is transparent and 100 is opaque.<br>
29   * <b>ANTIALIASING</b>: ['true" | "false"] turns antialiasing on/off. default is off.<br>
30   * <b>FONT_URI</b>: A URI pointing to the location of your truetype font resource, i.e. "file:///myfont.ttf"<br>
31   * <b>FONT_SIZE</b>: font size, i.e "12" (the default).<br>
32   * <b>FONT_STYLE</b>: [plain | italic | bold]<br>
33   * <b>ORIENTATION</b>: [0-360] where values are degrees. For convenience, use the constants defined, i.e "ORIENTATION_NORTH" (the default)<br>
34   * <b>POSITION</b>: [1-9] aligns the text following a numeric keypad layout. Default is [9] POSITION_BOTTOM_RIGHT<br>
35   * <b>BORDEROFFSET</b>: a numeric value indicating how many pixels to offset the text from it's nearest border<br>
36   */
37  public class TextOverlayFilter extends ConfigurableImageFilter {
38      public static final String TEXT = "TEXT";
39      public static final String DEFAULT_TEXT = "sample";
40  
41      public static final String COLOR = "COLOR";
42      public static final String DEFAULT_COLOR = "000000";
43  
44      public static final String OPACITY = "OPACITY";
45      public static final String DEFAULT_OPACITY = "100";
46  
47      public static final String ANTIALIASING = "ANTIALIASING";
48      public static final String DEFAULT_ANTIALIASING = "false";
49  
50      public static final String ORIENTATION = "ORIENTATION";
51      public static final String ORIENTATION_NORTH = "0";
52      public static final String ORIENTATION_NORTHEAST = "45";
53      public static final String ORIENTATION_EAST = "90";
54      public static final String ORIENTATION_SOUTHEAST = "135";
55      public static final String ORIENTATION_SOUTH = "180";
56      public static final String ORIENTATION_SOUTHWEST = "225";
57      public static final String ORIENTATION_WEST = "270";
58      public static final String ORIENTATION_NORTHWEST = "315";
59      public static final String DEFAULT_ORIENTATION = ORIENTATION_NORTH;
60  
61      public static final String FONT_URI = "FONT_URI";
62      public static final String DEFAULT_FONT = "dialog";
63  
64      public static final String FONT_SIZE = "FONT_SIZE";
65      public static final String DEFAULT_FONT_SIZE = "12";
66  
67      public static final String FONT_STYLE = "FONT_STYLE";
68      public static final String FONTSTYLE_PLAIN = "plain";
69      public static final String FONTSTYLE_BOLD = "bold";
70      public static final String FONTSTYLE_ITALIC = "italic";
71      public static final String DEFAULT_FONT_STYLE = FONTSTYLE_PLAIN;
72  
73      public static final String POSITION = "POSITION";
74      public static final String POSITION_TOP_LEFT = "1";
75      public static final String POSITION_TOP_CENTER = "2";
76      public static final String POSITION_TOP_RIGHT = "3";
77      public static final String POSITION_CENTER_LEFT = "4";
78      public static final String POSITION_CENTER = "5";
79      public static final String POSITION_CENTER_RIGHT = "6";
80      public static final String POSITION_BOTTOM_LEFT = "7";
81      public static final String POSITION_BOTTOM_CENTER = "8";
82      public static final String POSITION_BOTTOM_RIGHT = "9";
83  
84      public static final String BORDEROFFSET = "BORDEROFFSET";
85      public static final String DEFAULT_BORDEROFFSET = "0";
86  
87      private static final String FONT_ERROR = "unable to create font from URI: ";
88      private static final String FONTSIZE_ERROR = "font size out of range: ";
89      private static final String OPACITY_RANGE_ERROR = "alpha value only allowed ranging from 0 - 100, out of range error: ";
90      private static final String EMPTY_TEXT_ERROR = "text cannot be null or empty string";
91      private static final String POSITION_RANGE_ERROR = " values allowed only from range 1-9, out of range error: ";
92      private static final String ORIENTATION_RANGE_ERROR = " values allowed only from range 0-360, out of range error: ";
93  
94      protected Color textColor;
95      protected Font font;
96      protected float fontSize;
97      protected String text;
98      protected String position;
99      protected int xyO;
100     protected RenderingHints renderingHints;
101     protected boolean antiAliasing;
102     protected double orientation;
103     protected int alpha;
104 
105     protected static Logger log = Logger.getLogger(TextOverlayFilter.class.getName());
106 
107     public TextOverlayFilter() {
108         //do nothing
109     }
110 
111     /***
112      * Initialize the ImageFilter
113      */
114     public void initialize(Properties filterProperties) throws org.jmage.filter.FilterException {
115         try {
116             //color
117             alpha = Integer.decode(filterProperties.getProperty(OPACITY, DEFAULT_OPACITY)).intValue();
118             assert(alpha >= 0 && alpha <= 100) : OPACITY_RANGE_ERROR + alpha;
119             alpha *= 2.55f;
120 
121             String color = filterProperties.getProperty(COLOR, DEFAULT_COLOR);
122             int[] colors = ColorUtil.decodeRGBString(color);
123             this.textColor = new Color(colors[0], colors[1], colors[2], alpha);
124 
125             //text
126             text = filterProperties.getProperty(TEXT, DEFAULT_TEXT);
127             assert (text != null && !(text.length() == 0)) : EMPTY_TEXT_ERROR;
128 
129             //font
130             ResourceManager resourceManager = ApplicationContext.getContext().obtainResourceManager();
131             String fontUri = filterProperties.getProperty(FONT_URI);
132             if (fontUri != null) {
133                 URI uri = new URI(fontUri);
134                 font = (Font) resourceManager.createFrom(uri);
135                 //important to always return the resourceManager else it times out and causes delays.
136                 ApplicationContext.getContext().releaseResourceManager(resourceManager);
137             } else {
138                 font = new Font(DEFAULT_FONT, Font.PLAIN, Integer.valueOf(DEFAULT_FONT_SIZE).intValue());
139             }
140             assert(font != null) : FONT_ERROR + fontUri;
141 
142             //font size
143             fontSize = Float.valueOf(filterProperties.getProperty(FONT_SIZE, DEFAULT_FONT_SIZE)).floatValue();
144             assert(fontSize >= 1) : FONTSIZE_ERROR + fontSize;
145             font = font.deriveFont(fontSize);
146 
147             //font style
148             String style = filterProperties.getProperty(FONT_STYLE, DEFAULT_FONT_STYLE);
149             if (FONTSTYLE_PLAIN.equals(style.toLowerCase())) {
150                 font = font.deriveFont(Font.PLAIN);
151             }
152             if (FONTSTYLE_BOLD.equals(style.toLowerCase())) {
153                 font = font.deriveFont(Font.BOLD);
154             }
155             if (FONTSTYLE_ITALIC.equals(style.toLowerCase())) {
156                 font = font.deriveFont(Font.ITALIC);
157             }
158 
159             //position
160             position = filterProperties.getProperty(POSITION, POSITION_BOTTOM_RIGHT);
161             int pos = Integer.decode(position).intValue();
162             assert (pos >= 1 && pos <= 9) : POSITION + POSITION_RANGE_ERROR + position;
163 
164             xyO = Integer.decode(filterProperties.getProperty(BORDEROFFSET, DEFAULT_BORDEROFFSET)).intValue();
165 
166             //orientation
167             orientation = Double.valueOf(filterProperties.getProperty(ORIENTATION, DEFAULT_ORIENTATION)).doubleValue();
168             assert(orientation >= 0 && orientation <= 360) : ORIENTATION + ORIENTATION_RANGE_ERROR + orientation;
169             orientation /= 180f;
170 
171             //antialiasing
172             antiAliasing = Boolean.valueOf(filterProperties.getProperty(ANTIALIASING, DEFAULT_ANTIALIASING)).booleanValue();
173             HashMap paramMap = new HashMap();
174             if (antiAliasing) {
175                 paramMap.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
176                 paramMap.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
177             }
178 
179             renderingHints = new RenderingHints(paramMap);
180 
181             this.filterProperties = filterProperties;
182             if (log.isDebugEnabled()) log.debug(INITIALIZED);
183         } catch (Throwable t) {
184             String message = NOT_INITIALIZED + t.getMessage();
185             this.filterProperties = null;
186             if (log.isEnabledFor(Priority.ERROR)) log.error(message);
187             throw new FilterException(message);
188         }
189     }
190 
191     /***
192      * Overlay image with a String text.
193      *
194      * @throws org.jmage.filter.FilterException
195      *          if an error occurs during filtering
196      */
197     public PlanarImage filter(PlanarImage image) throws FilterException {
198         // Create BufferedImage, Graphics2D object and FontRenderContext for AWT transformation
199         BufferedImage bufferedImage = image.getAsBufferedImage();
200         Graphics2D graphics = (Graphics2D) bufferedImage.createGraphics();
201         FontRenderContext context = graphics.getFontRenderContext();
202         graphics.setRenderingHints(renderingHints);
203 
204         //align image
205         int[] coords = this.alignTextFor(bufferedImage, graphics, context);
206 
207         // set font, color, rotation and finally draw text
208         graphics.setColor(textColor);
209         graphics.setFont(font);
210         graphics.drawString(text, coords[0], coords[1]);
211         graphics.dispose();
212 
213         // Convert back and return
214         return (PlanarImage) JAI.create("AWTImage", (Image) bufferedImage);
215     }
216 
217     private int[] alignTextFor(BufferedImage image, Graphics2D graphics, FontRenderContext context) {
218         // Init positioning
219         int x = 0, y = 0;
220         int w0 = image.getWidth(), h0 = image.getHeight();
221         TextLayout tl = new TextLayout(text, font, context);
222         Rectangle2D rec = tl.getBounds();
223         int h1 = (int) rec.getHeight(), w1 = (int) rec.getWidth();
224 
225         // align text
226         //TODO: rotations do not get aligned properly, except for center position.
227         if (POSITION_BOTTOM_RIGHT.equals(position)) {
228             x = w0 - w1 - xyO;
229             y = h0 - xyO;
230         }
231         if (POSITION_BOTTOM_CENTER.equals(position)) {
232             x = (w0 / 2) - (w1 / 2);
233             y = h0 - xyO;
234         }
235         if (POSITION_BOTTOM_LEFT.equals(position)) {
236             x = 0 + xyO;
237             y = h0 - xyO;
238         }
239         if (POSITION_CENTER_RIGHT.equals(position)) {
240             x = w0 - w1 - xyO;
241             y = (h0 / 2) + (h1 / 2);
242         }
243         if (POSITION_CENTER.equals(position)) {
244             x = (w0 / 2) - (w1 / 2);
245             y = (h0 / 2) + (h1 / 2);
246         }
247         if (POSITION_CENTER_LEFT.equals(position)) {
248             x = 0 + xyO;
249             y = (h0 / 2) + (h1 / 2);
250         }
251         if (POSITION_TOP_RIGHT.equals(position)) {
252             x = w0 - w1 - xyO;
253             y = 0 + h1 + xyO;
254         }
255         if (POSITION_TOP_CENTER.equals(position)) {
256             x = (w0 / 2) - (w1 / 2);
257             y = 0 + h1 + xyO;
258         }
259         if (POSITION_TOP_LEFT.equals(position)) {
260             x = 0 + xyO;
261             y = 0 + h1 + xyO;
262         }
263 
264         //rotate
265         graphics.rotate(Math.PI * orientation, x + (w1 / 2), y - (h1 / 2));
266 
267         return new int[]{x, y};
268     }
269 }