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
109 }
110
111 /***
112 * Initialize the ImageFilter
113 */
114 public void initialize(Properties filterProperties) throws org.jmage.filter.FilterException {
115 try {
116
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
126 text = filterProperties.getProperty(TEXT, DEFAULT_TEXT);
127 assert (text != null && !(text.length() == 0)) : EMPTY_TEXT_ERROR;
128
129
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
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
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
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
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
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
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
199 BufferedImage bufferedImage = image.getAsBufferedImage();
200 Graphics2D graphics = (Graphics2D) bufferedImage.createGraphics();
201 FontRenderContext context = graphics.getFontRenderContext();
202 graphics.setRenderingHints(renderingHints);
203
204
205 int[] coords = this.alignTextFor(bufferedImage, graphics, context);
206
207
208 graphics.setColor(textColor);
209 graphics.setFont(font);
210 graphics.drawString(text, coords[0], coords[1]);
211 graphics.dispose();
212
213
214 return (PlanarImage) JAI.create("AWTImage", (Image) bufferedImage);
215 }
216
217 private int[] alignTextFor(BufferedImage image, Graphics2D graphics, FontRenderContext context) {
218
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
226
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
265 graphics.rotate(Math.PI * orientation, x + (w1 / 2), y - (h1 / 2));
266
267 return new int[]{x, y};
268 }
269 }