KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > util > images > Imaging


1 /*
2
3 This software is OSI Certified Open Source Software.
4 OSI Certified is a certification mark of the Open Source Initiative.
5
6 The license (Mozilla version 1.0) can be read at the MMBase site.
7 See http://www.MMBase.org/license
8
9 */

10 package org.mmbase.util.images;
11
12 import java.util.*;
13 import java.util.regex.*;
14 import org.mmbase.util.logging.Logging;
15 import org.mmbase.util.logging.Logger;
16
17 import java.io.*;
18
19 /**
20  * Utilities related to Images.
21  *
22  * @author Michiel Meeuwissen
23  */

24
25
26 public abstract class Imaging {
27
28     private static final Logger log = Logging.getLoggerInstance(Imaging.class);
29
30     public static final String JavaDoc FIELD_HANDLE = "handle";
31     public static final String JavaDoc FIELD_CKEY = "ckey";
32
33
34     /**
35      * Returns the mimetype using ServletContext.getServletContext which returns the servlet context
36      * @param ext A String containing the extension.
37      * @return The mimetype.
38      */

39     public static String JavaDoc getMimeTypeByExtension(String JavaDoc ext) {
40         return getMimeTypeByFileName("dummy." + ext);
41     }
42
43     public static String JavaDoc getMimeTypeByFileName(String JavaDoc fileName) {
44         javax.servlet.ServletContext JavaDoc sx = org.mmbase.module.core.MMBaseContext.getServletContext();
45         String JavaDoc mimeType = sx.getMimeType(fileName);
46         if (mimeType == null) {
47             log.warn("Can't find mimetype for a file with name '" + fileName + "'. Defaulting to text/html");
48             log.warn(Logging.stackTrace());
49             mimeType = "text/html";
50         }
51         return mimeType;
52     }
53
54     /**
55      * MMBase has some abreviations to convert commands, like 's' for 'geometry'. These are treated here.
56      * @param a alias
57      * @return actual convert parameter name for alias.
58      */

59     public static String JavaDoc getAlias(String JavaDoc a) {
60         if (a.equals("s")) return "geometry";
61         if (a.equals("r")) return "rotate";
62         if (a.equals("c")) return "colors";
63         if (a.equals("t")) return "transparent";
64         if (a.equals("i")) return "interlace";
65         if (a.equals("q")) return "quality";
66         if (a.equals("mono")) return "monochrome";
67         if (a.equals("highcontrast")) return "contrast";
68         if (a.equals("flipx")) return "flop";
69         if (a.equals("flipy")) return "flip";
70         // I don't think that this makes any sense, I dia is not dianegative,
71
// can be diapositive as well... But well, we are backwards compatible.
72
if (a.equals("dia")) return "negate";
73         return a;
74
75     }
76
77     private static final char NOQUOTING = '-';
78     /**
79      * Parses the 'image conversion template' to a List. I.e. it break
80      * it up in substrings, with '+' delimiter. However a + char does
81      * not count if it is somewhere between brackets (). Brackets nor
82      * +-chars count if they are in quotes (single or double)
83      *
84      * @since MMBase-1.7
85      */

86     // @author michiel
87
public static List parseTemplate(String JavaDoc template) {
88         if (log.isDebugEnabled()) log.debug("parsing " + template);
89         List params = new ArrayList();
90         if (template != null) {
91             int bracketDepth = 0;
92             char quoteState = NOQUOTING; // can be - (not in quote), ' or ".
93
StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
94
95             int i = 0;
96             while (i < template.length() && template.charAt(i) == '+') i++; // ignoring leading +'es (can sometimes be one)
97
for (; i < template.length(); i++) {
98                 char c = template.charAt(i);
99                 switch(c) {
100                 case '\'':
101                 case '"':
102                     if (quoteState == c) {
103                         quoteState = NOQUOTING;
104                     } else if (quoteState == NOQUOTING) {
105                         quoteState = c;
106                     }
107                     break;
108                 case '(': if (quoteState == NOQUOTING) bracketDepth++; break;
109                 case ')': if (quoteState == NOQUOTING) bracketDepth--; break;
110                 case '+': // command separator
111
if (bracketDepth == 0 // ignore if between brackets
112
&& quoteState == NOQUOTING // ignore if between quotes
113
) {
114                         removeSurroundingQuotes(buf);
115                         params.add(buf.toString());
116                         buf.setLength(0);
117                         continue;
118                     }
119                     break;
120                 }
121
122                 buf.append(c);
123             }
124             if (bracketDepth != 0) log.warn("Unbalanced brackets in " + template);
125             if (quoteState != NOQUOTING) log.warn("Unbalanced quotes in " + template);
126
127             removeSurroundingQuotes(buf);
128             if (! buf.toString().equals("")) params.add(buf.toString());
129         }
130         return params;
131     }
132     /**
133      * Just a utitility function, used by the function above.
134      * @since MMBase-1.7
135      */

136     protected static void removeSurroundingQuotes(StringBuffer JavaDoc buf) {
137         // remove surrounding quotes --> "+contrast" will be changed to +contrast
138
if (buf.length() >= 2 && (buf.charAt(0) == '"' || buf.charAt(0) == '\'') && buf.charAt(buf.length() - 1) == buf.charAt(0)) {
139             buf.deleteCharAt(0);
140             buf.deleteCharAt(buf.length() - 1);
141         }
142     }
143
144     /**
145      * Only used in legacy-support and perhaps debug code.
146      */

147     public static String JavaDoc unparseTemplate(List params) {
148         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
149         Iterator i = params.iterator();
150         while (i.hasNext()) {
151             buf.append(i.next());
152             if (i.hasNext()) {
153                 buf.append('+');
154             }
155         }
156         return buf.toString();
157     }
158
159
160     public static final Pattern GEOMETRY = Pattern.compile("(\\d*)([\\%\\!\\<\\>\\@]*)");
161
162     /**
163      * Predict the size of a image after converting it with the given parameters. This will not
164      * actually trigger a conversion, but only calculate the dimension of the result, so only
165      * involves some basic arithmetic and string-parsing.
166      *
167      * Most transformations which alter the dimension of an image are supported: geometry, border, rotate, part.
168      *
169      * Probably because of different rounding strategies, there is sometimes a difference of one or
170      * two pixels beteen the prediction and/or the result of ImageMagick and/or JAI.
171      */

172     public static Dimension predictDimension(Dimension originalSize, List params) {
173
174         Dimension dim = new Dimension(originalSize);
175         String JavaDoc gravity = "West";
176         Iterator i = params.iterator();
177         while (i.hasNext()) {
178             String JavaDoc key = (String JavaDoc)i.next();
179             int pos = key.indexOf('(');
180             int pos2 = key.lastIndexOf(')');
181             if (pos != -1 && pos2 != -1) {
182                 String JavaDoc type = key.substring(0, pos);
183                 String JavaDoc cmd = key.substring(pos + 1, pos2);
184                 String JavaDoc[] tokens = cmd.split("[x,\\n\\r]");
185                 if (log.isDebugEnabled()) {
186                     log.debug("getCommands(): type=" + type + " cmd=" + cmd);
187                 }
188                 // Following code translates some MMBase specific things
189
// to imagemagick's convert arguments.
190
// using this conversion ensures compatibility between systems
191
type = getAlias(type);
192                 if (type.equals("geometry")) {
193                     String JavaDoc xString = tokens.length > 0 ? tokens[0] : "";
194                     String JavaDoc yString = tokens.length > 1 ? tokens[1] : "";
195
196                     Matcher matchX = GEOMETRY.matcher(xString);
197                     xString = matchX.matches() ? matchX.group(1) : "";
198                     Matcher matchY = GEOMETRY.matcher(yString);
199                     yString = matchY.matches() ? matchY.group(1) : "";
200                     
201
202                     String JavaDoc options = (matchX.matches() ? matchX.group(2) : "") + (matchY.matches() ? matchY.group(2) : "");
203
204                     boolean aspectRatio = true;
205                     boolean area = false;
206                     boolean percentage = false;
207                     boolean onlyWhenOneBigger = false;
208                     boolean onlyWhenBothSmaller = false;
209
210                     for (int j = 0 ; j < options.length(); j++) {
211                         char o = options.charAt(j);
212                         if (o == '%') percentage = true;
213                         if (o == '@') area = true;
214                         if (o == '!') aspectRatio = false;
215                         if (o == '>') onlyWhenOneBigger = true;
216                         if (o == '<') onlyWhenBothSmaller = true;
217                     }
218                     
219                     int x = "".equals(xString) ? 0 : Integer.parseInt(xString);
220                     int y = "".equals(yString) ? 0 : Integer.parseInt(yString);
221
222
223                     if (percentage) {
224                         x *= (float) dim.x / 100.0;
225                         y *= (float) dim.y / 100.0;
226                         aspectRatio = false;
227                     }
228                     if (x == 0) {
229                         x = Math.round((float) dim.x * y / dim.y);
230                     }
231
232                     if (area) {
233                         float a = x;
234                         if (dim.getArea() > a) {
235                             float ratio = (float) dim.x / dim.y;
236                             x = (int) Math.floor(Math.sqrt(a * ratio));
237                             y = (int) Math.floor(Math.sqrt(a / ratio));
238                         } else {
239                             x = dim.x;
240                             y = dim.y;
241                         }
242                         aspectRatio = false; // simply copy this;
243
}
244
245                     if (y == 0) {
246                         y = Math.round((float) dim.y * x / dim.x);
247                     }
248
249
250                     boolean skipScale =
251                         (onlyWhenOneBigger && dim.x < x && dim.y < y) ||
252                         (onlyWhenBothSmaller && (dim.x > x || dim.y > y));
253
254                     if (! skipScale) {
255                         if (! aspectRatio) {
256                             dim.x = x;
257                             dim.y = y;
258                         } else {
259                             if ((float)dim.x/dim.y > (float)x / y) {
260                                 dim.y *= ((float) x / dim.x);
261                                 dim.x = x;
262                             } else {
263                                 dim.x *= ((float) y / dim.y);
264                                 dim.y = y;
265                             }
266                         }
267                     }
268                 } else if (type.equals("border")) {
269                     int x = tokens.length > 0 ? Integer.parseInt(tokens[0]) : 0;
270                     int y = tokens.length > 1 ? Integer.parseInt(tokens[1]) : x;
271                     dim.x += 2 * x;
272                     dim.y += 2 * y;
273                 } else if (type.equals("rotate")) {
274                     double degrees = Double.parseDouble(tokens[0]);
275                     double a = Math.toRadians(degrees); //
276
double xorg = dim.x;
277                     double yorg = dim.y;
278                     dim.x = (int) Math.round(Math.abs(Math.cos(a)) * xorg + Math.abs(Math.sin(a) * yorg));
279                     dim.y = (int) Math.round(Math.abs(Math.sin(a)) * xorg + Math.abs(Math.cos(a) * yorg));
280
281                 } else if (type.equals("gravity")) {
282                     gravity = cmd;
283                 } else if (type.equals("chop")) {
284                 } else if (type.equals("shave")) {
285                 } else if (type.equals("crop")) {
286                 } else if (type.equals("part")) {
287                     int x1 = Integer.parseInt(tokens[0]);
288                     int y1 = Integer.parseInt(tokens[1]);
289                     int x2 = Integer.parseInt(tokens[2]);
290                     int y2 = Integer.parseInt(tokens[3]);
291                     if (x2 > dim.x) x2 = dim.x;
292                     if (y2 > dim.y) y2 = dim.y;
293                     if (x1 > x2) x1 = x2;
294                     if (y1 > y2) y1 = y2;
295                     dim.x = x2 - x1;
296                     dim.y = y2 - y1;
297                 }
298             }
299                 
300         }
301         return dim;
302     }
303
304     /**
305      * Tries to predict the new file-size after conversion. The result (in the current
306      * implementation) is very unreliable, because it assumes that the file-size is proportional to the area.
307      */

308     public static int predictFileSize(Dimension originalDimension, int originalFileSize, Dimension predictedDimension) {
309         // hard, lets guess that the file-size is proportional to the area of an image.
310
return originalFileSize * predictedDimension.getArea() / originalDimension.getArea();
311         
312
313     }
314
315     /**
316      * Parses a ckey String into a CKey structure.
317      */

318     public static CKey parseCKey(String JavaDoc ckey) {
319         int pos = 0;
320         while (Character.isDigit(ckey.charAt(pos))) pos ++;
321         return new CKey(Integer.parseInt(ckey.substring(0, pos)), ckey.substring(pos));
322     }
323
324     /**
325      * Structure with node-number and template.
326      */

327     public static class CKey {
328         public String JavaDoc template;
329         public int node;
330         CKey(int n, String JavaDoc t) {
331             template = t;
332             node = n;
333         }
334         public String JavaDoc toString() {
335             return "" + node + template;
336         }
337     }
338
339
340     /**
341      * main is only for testing.
342      */

343     public static void main(String JavaDoc[] args) {
344         try {
345             File file = new File(args[0]);
346             FileInputStream input = new FileInputStream(file);
347             ByteArrayOutputStream bytes = new ByteArrayOutputStream();
348             int b = input.read();
349             while (b != -1) {
350                 bytes.write(b);
351                 b = input.read();
352             }
353             input.close();
354             byte[] ba = bytes.toByteArray();
355             ImageInformer informer = new ImageMagickImageInformer();
356             
357             Dimension originalSize = informer.getDimension(ba);
358
359             ImageConverter converter1 = new ImageMagickImageConverter();
360             ImageConverter converter2 = new JAIImageConverter();
361             
362
363             String JavaDoc[] templates = {
364                 "s(100x60)+f(jpeg)",
365                 "part(10x10x30x50)",
366                 "part(10x10x2000x2000)",
367                 "s(10000@)", "s(100x100@)",
368                 "s(10000x2000>)", "s(100000x2000<)",
369                 "s(4x5<)", "s(4x5>)",
370                 "r(90)", "r(45)", "r(198)", "r(-30)",
371                 "border(5)", "border(5x8)",
372                 "r(45)+border(10x20)",
373                 "flip",
374                 "s(100)", "s(x100)", "s(10x70)", "s(70x10)", "s(60x70!)", "s(80%x150%)",
375                 "s(100)+f(png)+r(20)+s(400x400)"
376             };
377
378             System.out.println("original size: " + originalSize);
379             System.out.println("template:predicted size:actual size (IM):actual size(JAI)");
380             for (int i = 0 ; i < templates.length; i++) {
381
382                 String JavaDoc template = templates[i];
383                 List params = parseTemplate(template);
384                 System.out.print(template + ":" + predictDimension(originalSize, params) + ":");
385                 try {
386                     System.out.print(informer.getDimension(converter1.convertImage(ba, null, params)));
387                 } catch (Exception JavaDoc e) {
388                     System.out.print(e.getMessage());
389                 }
390                 System.out.print(":");
391                 try {
392                     System.out.print(informer.getDimension(converter2.convertImage(ba, null, params)));
393                 } catch (Exception JavaDoc e) {
394                     System.out.print(e.getMessage());
395                 }
396                 System.out.println("");
397             }
398
399         } catch (IOException ioe) {
400             throw new RuntimeException JavaDoc(ioe);
401         }
402         
403     }
404
405 }
406
Popular Tags