1 10 package org.mmbase.util.images; 11 12 import java.util.*; 13 import java.io.*; 14 import java.util.regex.*; 15 16 import org.mmbase.util.externalprocess.CommandLauncher; 17 import org.mmbase.util.externalprocess.ProcessException; 18 import org.mmbase.util.Encode; 19 20 import org.mmbase.util.logging.Logging; 21 import org.mmbase.util.logging.Logger; 22 23 32 public class ImageMagickImageConverter implements ImageConverter { 33 private static final Logger log = Logging.getLoggerInstance(ImageMagickImageConverter.class); 34 35 private static final Pattern IM_VERSION_PATTERN = Pattern.compile("(?is).*?\\s(\\d+)\\.(\\d+)\\.(\\d+)\\s.*"); 36 37 private int imVersionMajor = 6; 38 private int imVersionMinor = 2; 39 private int imVersionPatch = 4; 40 41 private static String converterPath = "convert"; 44 private static int colorizeHexScale = 100; 45 private static int modulateScaleBase = Integer.MAX_VALUE; 48 49 51 52 58 public void init(Map params) { 59 String converterRoot = ""; 60 String converterCommand = "convert"; 61 62 String tmp; 63 tmp = (String ) params.get("ImageConvert.ConverterRoot"); 64 if (tmp != null && ! tmp.equals("")) { 65 converterRoot = tmp; 66 } 67 68 tmp = (String ) params.get("ImageConvert.ConverterCommand"); 69 if (tmp != null && ! tmp.equals("")) { 70 converterCommand = tmp; 71 } 72 73 if(System.getProperty("os.name") != null && System.getProperty("os.name").startsWith("Windows")) { 74 if (!converterCommand.endsWith(".exe")) { 77 converterCommand += ".exe"; 78 } 79 } 80 81 String configFile = params.get("configfile").toString(); 82 if (configFile == null) configFile = "images builder xml"; 83 84 converterPath = converterCommand; if (!converterRoot.equals("")) { File checkConvDir = new File(converterRoot).getAbsoluteFile(); 88 if (!checkConvDir.exists()) { 89 log.error( "ImageConvert.ConverterRoot " + converterRoot + " in " + configFile + " does not exist"); 90 } else if (!checkConvDir.isDirectory()) { 91 log.error( "ImageConvert.ConverterRoot " + converterRoot + " in " + configFile + " is not a directory"); 92 } else { 93 File checkConvCom = new File(converterRoot, converterCommand); 95 converterPath = checkConvCom.toString(); 96 if (!checkConvCom.exists()) { 97 log.error( converterPath + " specified by " + configFile + " does not exist"); 98 } else if (!checkConvCom.isFile()) { 99 log.error( converterPath + " specified by " + configFile + " is not a file"); 100 } 101 } 102 } 103 107 111 ByteArrayOutputStream errorStream = new ByteArrayOutputStream(); 112 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 113 try { 114 CommandLauncher launcher = new CommandLauncher("ConvertImage"); 115 log.debug("Starting convert"); 116 List cmd = new ArrayList(); 117 cmd.add("-version"); 118 launcher.execute(converterPath, (String []) cmd.toArray(new String [] {})); 119 launcher.waitAndRead(outputStream, errorStream); 120 } catch (ProcessException e) { 121 log.error("Convert test failed. " + converterPath + " (" + e.toString() + ") conv.root='" + converterRoot 122 + "' conv.command='" + converterCommand + "'", e); 123 } 124 125 String imOutput = outputStream.toString(); 126 Matcher m = IM_VERSION_PATTERN.matcher(imOutput); 127 if (m.matches()) { 128 imVersionMajor = Integer.parseInt(m.group(1)); 129 imVersionMinor = Integer.parseInt(m.group(2)); 130 imVersionPatch = Integer.parseInt(m.group(3)); 131 log.info("Found ImageMagick version " + imVersionMajor + "." + imVersionMinor + "." + imVersionPatch); 132 } else { 133 log.error( "converter from location " + converterPath + ", gave strange result: " + imOutput 134 + "conv.root='" + converterRoot + "' conv.command='" + converterCommand + "'"); 135 log.info("Supposing ImageMagick version " + imVersionMajor + "." + imVersionMinor + "." + imVersionPatch); 136 137 } 138 139 tmp = (String ) params.get("ImageConvert.ColorizeHexScale"); 141 if (tmp != null) { 142 try { 143 colorizeHexScale = Integer.parseInt(tmp); 144 } catch (NumberFormatException e) { 145 log.error( "Property ImageConvert.ColorizeHexScale should be an integer: " + e.toString() + "conv.root='" + converterRoot + "' conv.command='" + converterCommand + "'"); 146 } 147 } 148 log.debug("Searching for ModulateScaleBase property."); 150 tmp = (String ) params.get("ImageConvert.ModulateScaleBase"); 151 if (tmp != null) { 152 try { 153 modulateScaleBase = Integer.parseInt(tmp); 154 } catch (NumberFormatException nfe) { 155 log.error( "Property ImageConvert.ModulateScaleBase should be an integer, instead of:'" + tmp + "'" + ", conv.root='" + converterRoot + "' conv.command='" + converterCommand + "'"); 156 log.error("Ignoring modulateScaleBase property."); 157 log.error(nfe.getMessage()); 158 } 159 } else { 160 log.debug( 161 "ModulateScaleBase property not found, ignoring the modulateScaleBase."); 162 } 163 } 164 165 private static class ParseResult { 166 List args; 167 String format; 168 File cwd; 169 } 170 171 179 public byte[] convertImage(byte[] input, String sourceFormat, List commands) { 180 byte[] pict = null; 181 if (commands != null && input != null) { 182 ParseResult parsedCommands = getConvertCommands(commands); 183 if (parsedCommands.format.equals("asis") && sourceFormat != null) { 184 parsedCommands.format = sourceFormat; 185 } 186 pict = convertImage(input, parsedCommands.args, parsedCommands.format, parsedCommands.cwd); 187 } 188 return pict; 189 } 190 191 194 protected String color(String c) { 195 if (c.charAt(0) == 'X') { 196 c = '#' + c.substring(1); } 199 if (c.length() == 6) { 200 return "#" + c.toLowerCase(); 202 } else { 203 return c.toLowerCase(); 204 } 205 } 206 207 208 213 private ParseResult getConvertCommands(List params) { 214 if (log.isDebugEnabled()) { 215 log.debug("getting convert commands from " + params); 216 } 217 ParseResult result = new ParseResult(); 218 List cmds = new ArrayList(); 219 result.args = cmds; 220 result.cwd = null; 221 result.format = Factory.getDefaultImageFormat(); 222 223 String key, type; 224 String cmd; 225 int pos, pos2; 226 Iterator t = params.iterator(); 227 while (t.hasNext()) { 228 key = (String ) t.next(); 229 if (log.isDebugEnabled()) log.debug("parsing '" + key + "'"); 230 pos = key.indexOf('('); 231 pos2 = key.lastIndexOf(')'); 232 if (pos != -1 && pos2 != -1) { 233 type = key.substring(0, pos).toLowerCase(); 234 cmd = key.substring(pos + 1, pos2); 235 if (log.isDebugEnabled()) { 236 log.debug("getCommands(): type=" + type + " cmd=" + cmd); 237 } 238 type = Imaging.getAlias(type); 240 if (type.equals("modulate") && (modulateScaleBase != Integer.MAX_VALUE)) { 242 cmd = calculateModulateCmd(cmd, modulateScaleBase); 243 } else if (type.equals("colorizehex")) { 244 if (log.isDebugEnabled()) 247 log.debug("colorizehex, cmd: " + cmd); 248 String hex = cmd; 249 if (hex.length() == 6) { 251 252 int r = colorizeHexScale - Math.round( colorizeHexScale * Integer.parseInt( hex.substring(0, 2), 16) / 255.0f); 254 int g = colorizeHexScale - Math.round( colorizeHexScale * Integer.parseInt( hex.substring(2, 4), 16) / 255.0f); 255 int b = colorizeHexScale - Math.round( colorizeHexScale * Integer.parseInt( hex.substring(4, 6), 16) / 255.0f); 256 if (log.isDebugEnabled()) { 257 log.debug("Hex is :" + hex); 258 log.debug( "Calling colorize with r:" + r + " g:" + g + " b:" + b); 259 } 260 type = "colorize"; 261 cmd = r + "/" + g + "/" + b; 262 } 263 } else if (type.equals("gamma")) { 264 StringTokenizer tok = new StringTokenizer(cmd, ",/"); 265 String r = tok.nextToken(); 266 String g = tok.nextToken(); 267 String b = tok.nextToken(); 268 cmd = r + "/" + g + "/" + b; 269 } else if ( 270 type.equals("pen") 271 || type.equals("transparent") 272 || type.equals("fill") 273 || type.equals("bordercolor") 274 || type.equals("background") 275 || type.equals("box") 276 || type.equals("opaque") 277 || type.equals("stroke")) { 278 cmd = color(cmd); 280 } else if (type.equals("text")) { 281 int firstcomma = cmd.indexOf(','); 282 int secondcomma = cmd.indexOf(',', firstcomma + 1); 283 if (imVersionMajor < 6) { 284 type = "draw"; 285 try { 286 File tempFile = File.createTempFile("mmbase_image_text_", null); 287 tempFile.deleteOnExit(); 288 Encode encoder = new Encode("ESCAPE_SINGLE_QUOTE"); 289 String text = cmd.substring(secondcomma + 1); 290 FileOutputStream tempFileOutputStream = new FileOutputStream(tempFile); 291 tempFileOutputStream.write(encoder.decode(text.substring(1, text.length() - 1)).getBytes("UTF-8")); 292 tempFileOutputStream.close(); 293 cmd = "text " + cmd.substring(0, secondcomma) + " '@" + tempFile.getPath() + "'"; 294 } catch (IOException e) { 295 log.error("Could not create temporary file for text: " + e.toString()); 296 cmd = "text " + cmd.substring(0, secondcomma) + " 'Could not create temporary file for text.'"; 297 } 298 } else { 299 cmds.add("-encoding"); 300 cmds.add("unicode"); 301 cmds.add("-annotate"); 302 try { 303 File tempFile = File.createTempFile("mmbase_image_text_", null); 304 tempFile.deleteOnExit(); 305 FileOutputStream tempFileOutputStream = new FileOutputStream(tempFile); 306 Encode encoder = new Encode("ESCAPE_SINGLE_QUOTE"); 307 String text = cmd.substring(secondcomma + 1); 308 log.debug("Using '" + text + "'"); 309 tempFileOutputStream.write(encoder.decode(text.substring(1, text.length() - 1)).getBytes("UTF-8")); 310 tempFileOutputStream.close(); 311 cmds.add("+" + cmd.substring(0, firstcomma) + "+" + cmd.substring(firstcomma + 1, secondcomma)); 312 cmds.add("@" + tempFile.getPath()); 313 } catch (IOException e) { 314 log.error("Could not create temporary file for text: " + e.toString()); 315 cmd = cmd.substring(0, secondcomma) + " 'Could not create temporary file for text.'"; 316 } 317 continue; 318 } 319 } else if (type.equals("draw")) { 320 } else if (type.equals("font")) { 327 if (cmd.startsWith("mm:")) { 328 cmd = org.mmbase.module.core.MMBaseContext.getConfigPath()+ File.separator + cmd.substring(3); 330 } 331 File fontFile = new File(cmd); 332 if (!fontFile.isFile()) { 333 File fontDir = 335 new File( org.mmbase.module.core.MMBaseContext.getConfigPath(),"fonts"); 336 if (fontDir.isDirectory()) { 337 if (log.isDebugEnabled()) { 338 log.debug("Using " + fontDir + " as working dir for conversion. A 'type.mgk' (see ImageMagick documentation) can be in this dir to define fonts"); 339 } 340 result.cwd = fontDir; 341 } else { 342 log.debug( 343 "Using named font without MMBase 'fonts' directory, using ImageMagick defaults only"); 344 } 345 } 346 347 } else if (type.equals("circle")) { 348 type = "draw"; 349 cmd = "circle " + cmd; 350 } else if (type.equals("part")) { 351 StringTokenizer tok = new StringTokenizer(cmd, "x,\n\r"); 352 try { 353 int x1 = Integer.parseInt(tok.nextToken()); 354 int y1 = Integer.parseInt(tok.nextToken()); 355 int x2 = Integer.parseInt(tok.nextToken()); 356 int y2 = Integer.parseInt(tok.nextToken()); 357 type = "crop"; 358 cmd = (x2 - x1) + "x" + (y2 - y1) + "+" + x1 + "+" + y1; 359 } catch (Exception e) { 360 361 log.error(e.toString()); 362 } 363 } else if (type.equals("roll")) { 364 StringTokenizer tok = new StringTokenizer(cmd, "x,\n\r"); 365 String str; 366 int x = Integer.parseInt(tok.nextToken()); 367 int y = Integer.parseInt(tok.nextToken()); 368 if (x >= 0) 369 str = "+" + x; 370 else 371 str = "" + x; 372 if (y >= 0) 373 str += "+" + y; 374 else 375 str += "" + y; 376 cmd = str; 377 } else if (type.equals("f")) { 378 if (! (cmd.equals("asis") && result.format != null)) { 379 result.format = cmd; 380 } 381 continue; } 383 if (log.isDebugEnabled()) { 384 log.debug("adding " + type + " " + cmd); 385 } 386 if (! isCommandPrefixed(type)) { cmds.add("-" + type); 389 } else { 390 cmds.add(type); 391 } 392 cmds.add(cmd); 393 394 } else { 395 key = Imaging.getAlias(key); 396 if (key.equals("lowcontrast")) { 397 cmds.add("+contrast"); 398 } else if (key.equals("neg")) { 399 cmds.add("+negate"); 400 } else { 401 if (! isCommandPrefixed(key)) { cmds.add("-" + key); 403 } else { 404 cmds.add(key); 405 } 406 } 407 } 408 } 409 return result; 410 } 411 412 415 private boolean isCommandPrefixed(String s) { 416 if (s == null || s.length() == 0) return false; 417 char c = s.charAt(0); 418 return c == '-' || c == '+'; 419 } 420 421 435 private String calculateModulateCmd(String cmd, int scaleBase) { 436 log.debug( "Calculating modulate cmd using scale base " + scaleBase + " for modulate cmd: " + cmd); 437 String modCmd = ""; 438 StringTokenizer st = new StringTokenizer(cmd, ",/"); 439 while (st.hasMoreTokens()) { 440 modCmd += scaleBase + Integer.parseInt(st.nextToken()) + ","; 441 } 442 if (!modCmd.equals("")) { 443 modCmd = modCmd.substring(0, modCmd.length() - 1); 444 } 445 log.debug("Modulate cmd after calculation: " + modCmd); 447 return modCmd; 448 } 449 450 459 private byte[] convertImage(byte[] pict, List cmd, String format, File cwd) { 460 461 if (pict != null && pict.length > 0) { 462 cmd.add(0, "-"); 463 cmd.add(0, converterPath); 464 cmd.add(format+ ":-"); 465 466 String command = cmd.toString(); log.debug("Converting image(#" + pict.length + " bytes) to '" + format + "' ('" + command + "')"); 468 469 CommandLauncher launcher = new CommandLauncher("ConvertImage"); 470 ByteArrayOutputStream imageStream = new ByteArrayOutputStream(); 471 ByteArrayOutputStream errorStream = new ByteArrayOutputStream(); 472 ByteArrayInputStream originalStream = new ByteArrayInputStream(pict); 473 474 try { 475 if (cwd != null) { 476 String [] env = { "MAGICK_HOME=" + cwd.toString() }; 478 if (log.isDebugEnabled()) { 479 log.debug("MAGICK_HOME " + env[0]); 480 } 481 launcher.execute((String []) cmd.toArray(new String [0]), env); 482 } 483 else { 484 launcher.execute((String []) cmd.toArray(new String [0])); 485 } 486 launcher.waitAndWrite(originalStream, imageStream, errorStream); 487 488 log.debug("retrieved all information"); 489 byte[] image = imageStream.toByteArray(); 490 491 if (image.length < 1) { 492 log.error("Imagemagick conversion did not succeed. Returning null."); 496 String errorMessage = errorStream.toString(); 497 498 if (errorMessage.length() > 0) { 499 log.error( "From stderr with command '" + command + "' in '" + new File("").getAbsolutePath() + "' --> '" + errorMessage + "'"); 500 } else { 501 log.debug("No information on stderr found"); 502 } 503 return null; 504 } 505 else { 506 if (log.isServiceEnabled()) { 508 log.service("converted image(#" + pict.length + " bytes) to '" + format + "'-image(#" + image.length + " bytes)('" + command + "')"); 509 } 510 return image; 511 } 512 } 513 catch (ProcessException e) { 514 log.error("converting image with command: '" + command + "' failed with reason: '" + e.getMessage() + "'"); 515 log.error(Logging.stackTrace(e)); 516 } 517 finally { 518 try { 519 if (originalStream != null) { 520 originalStream.close(); 521 } 522 } 523 catch (IOException ioe) { 524 } 525 try { 526 if (imageStream != null) { 527 imageStream.close(); 528 } 529 } 530 catch (IOException ioe) { 531 } 532 } 533 } 534 else { 535 log.error("Converting an empty image does not make sense."); 536 } 537 538 return null; 539 } 540 541 } 542 | Popular Tags |