001: /***************************************************************
002: * This file is part of the [fleXive](R) project.
003: *
004: * Copyright (c) 1999-2008
005: * UCS - unique computing solutions gmbh (http://www.ucs.at)
006: * All rights reserved
007: *
008: * The [fleXive](R) project is free software; you can redistribute
009: * it and/or modify it under the terms of the GNU General Public
010: * License as published by the Free Software Foundation;
011: * either version 2 of the License, or (at your option) any
012: * later version.
013: *
014: * The GNU General Public License can be found at
015: * http://www.gnu.org/copyleft/gpl.html.
016: * A copy is found in the textfile GPL.txt and important notices to the
017: * license from the author are found in LICENSE.txt distributed with
018: * these libraries.
019: *
020: * This library is distributed in the hope that it will be useful,
021: * but WITHOUT ANY WARRANTY; without even the implied warranty of
022: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023: * GNU General Public License for more details.
024: *
025: * For further information about UCS - unique computing solutions gmbh,
026: * please see the company website: http://www.ucs.at
027: *
028: * For further information about [fleXive](R), please see the
029: * project website: http://www.flexive.org
030: *
031: *
032: * This copyright notice MUST APPEAR in all copies of the file!
033: ***************************************************************/package com.flexive.shared.media.impl;
034:
035: import com.flexive.shared.FxSharedUtils;
036: import com.flexive.shared.exceptions.FxApplicationException;
037: import org.apache.commons.logging.Log;
038: import org.apache.commons.logging.LogFactory;
039:
040: import javax.xml.stream.XMLOutputFactory;
041: import javax.xml.stream.XMLStreamException;
042: import javax.xml.stream.XMLStreamWriter;
043: import java.io.*;
044: import java.util.regex.Pattern;
045: import java.util.StringTokenizer;
046:
047: /**
048: * ImageMagick media engine
049: *
050: * @author Markus Plesser (markus.plesser@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
051: * @version $Rev
052: */
053: public class FxMediaImageMagickEngine {
054:
055: private static final transient Log LOG = LogFactory
056: .getLog(FxMediaImageMagickEngine.class);
057:
058: public static final boolean IM_AVAILABLE;
059: public static final boolean IM_IDENTIFY_POSSIBLE;
060: public static final String IM_VERSION;
061: public static final int IM_MAJOR;
062: public static final int IM_MINOR;
063: public static final int IM_SUB;
064:
065: static {
066: FxSharedUtils.ProcessResult res = FxSharedUtils.executeCommand(
067: "convert", "-version");
068: IM_AVAILABLE = !(res.getExitCode() != 0 || res.getStdOut()
069: .indexOf("ImageMagick") <= 0);
070:
071: if (IM_AVAILABLE) {
072: StringTokenizer tok = new StringTokenizer(res.getStdOut(),
073: " ", false);
074: if (tok.hasMoreElements())
075: tok.nextElement();
076: if (tok.hasMoreElements())
077: tok.nextElement();
078: if (tok.hasMoreElements())
079: IM_VERSION = (String) tok.nextElement();
080: else
081: IM_VERSION = "unknown";
082: String[] ver = IM_VERSION.split("\\.");
083: if (ver.length == 3) {
084: int i = 0, j = 0, k = 0;
085: try {
086: i = Integer.parseInt(ver[0]);
087: j = Integer.parseInt(ver[1]);
088: k = Integer.parseInt(ver[2]);
089: } catch (NumberFormatException e) {
090: LOG.error(e);
091: }
092: IM_MAJOR = i;
093: IM_MINOR = j;
094: IM_SUB = k;
095: } else {
096: IM_MAJOR = 0;
097: IM_MINOR = 0;
098: IM_SUB = 0;
099: }
100: } else {
101: IM_VERSION = "unknown";
102: IM_MAJOR = 0;
103: IM_MINOR = 0;
104: IM_SUB = 0;
105: }
106: IM_IDENTIFY_POSSIBLE = IM_AVAILABLE
107: && (IM_MAJOR > 6 || (IM_MAJOR == 6 && IM_MINOR >= 3));
108: }
109:
110: // Name of the identify executeable
111: public final static String IDENTIFY_BINARY = "identify";
112: // Name of the convert executeable
113: public final static String CONVERT_BINARY = "convert";
114:
115: /**
116: * Scale an image and return the dimensions (width and height) as int array
117: *
118: * @param original original file
119: * @param scaled scaled file
120: * @param extension extension
121: * @param width desired width
122: * @param height desired height
123: * @return actual width ([0]) and height ([1]) of scaled image
124: * @throws FxApplicationException on errors
125: */
126: public static int[] scale(File original, File scaled,
127: String extension, int width, int height)
128: throws FxApplicationException {
129: FxSharedUtils.ProcessResult res = FxSharedUtils.executeCommand(
130: CONVERT_BINARY, "-scale", width + "x" + height,
131: original.getAbsolutePath(), scaled.getAbsolutePath());
132: if (res.getExitCode() != 0)
133: throw new FxApplicationException(
134: "ex.executeCommand.failed", CONVERT_BINARY, res
135: .getStdErr());
136: res = FxSharedUtils.executeCommand(IDENTIFY_BINARY, "-ping",
137: FxSharedUtils.escapePath(scaled.getAbsolutePath()));
138: if (res.getExitCode() != 0)
139: throw new FxApplicationException(
140: "ex.executeCommand.failed", IDENTIFY_BINARY, res
141: .getStdErr());
142: return getPingDimensions(extension, res.getStdOut());
143: }
144:
145: /**
146: * Parse a ping response from ImageMagick for image dimensions
147: *
148: * @param extension extension of the file
149: * @param line the response from ImageMagick's ping command
150: * @return array containing dimensions or {0,0} if an error occured
151: */
152: public static int[] getPingDimensions(String extension, String line) {
153: try {
154: int start = 0;
155: if (extension.equals(".JPG"))
156: start = line.indexOf(" JPEG ") + 1;
157: if (start <= 0 && extension.equals(".PNG"))
158: start = line.indexOf(" PNG ") + 1;
159: if (start <= 0 && extension.equals(".GIF"))
160: start = line.indexOf(" GIF ") + 1;
161: if (start <= 0) {
162: String[] tmp = line.split(" ");
163: if (tmp[2].indexOf('x') > 0) {
164: String[] dim = tmp[2].split("x");
165: return new int[] { Integer.parseInt(dim[0]),
166: Integer.parseInt(dim[1]) };
167: }
168: }
169: if (start > 0) {
170: String[] data = line.substring(start).split(" ");
171: String[] dim = data[1].split("x");
172: return new int[] { Integer.parseInt(dim[0]),
173: Integer.parseInt(dim[1]) };
174: }
175: } catch (Exception e) {
176: return new int[] { 0, 0 };
177: }
178: return new int[] { 0, 0 };
179: }
180:
181: /**
182: * Get the identation depth of the current line (2 characters = 1 level)
183: *
184: * @param data line to examine
185: * @return identation depth
186: */
187: private static int getLevel(String data) {
188: if (data == null || data.length() == 0)
189: return 0;
190: int ident = 0;
191: for (int i = 0; i < data.length(); i++) {
192: if (data.charAt(i) != ' ')
193: return ident / 2;
194: ident++;
195: }
196: if (ident == 0)
197: return ident;
198: return ident / 2;
199: }
200:
201: static Pattern pNumeric = Pattern.compile("^\\s*\\d+\\: .*");
202: static Pattern pColormap = Pattern.compile("^\\s*Colormap\\: \\d+");
203:
204: // private final static Pattern pSkip = Pattern.compile("^\\s*\\d+\\:.*|^0x.*|^\\s*unknown.*|^\\s*Custom Field.*");
205:
206: /**
207: * Parse an identify stdOut result (from in) and convert it to an XML content
208: *
209: * @param in identify response
210: * @return XML content
211: * @throws XMLStreamException on errors
212: * @throws IOException on errors
213: */
214: public static String parse(InputStream in)
215: throws XMLStreamException, IOException {
216: StringWriter sw = new StringWriter(2000);
217: BufferedReader br = new BufferedReader(
218: new InputStreamReader(in));
219: XMLStreamWriter writer = XMLOutputFactory.newInstance()
220: .createXMLStreamWriter(sw);
221: writer.writeStartDocument();
222:
223: int lastLevel = 0, level, lastNonValueLevel = 1;
224: boolean valueEntry;
225: String curr = null;
226: String[] entry;
227: try {
228: while ((curr = br.readLine()) != null) {
229: level = getLevel(curr);
230: if (level == 0 && curr.startsWith("Image:")) {
231: writer.writeStartElement("Image");
232: entry = curr.split(": ");
233: if (entry.length >= 2)
234: writer.writeAttribute("source", entry[1]);
235: lastLevel = level;
236: continue;
237: }
238: if (!(valueEntry = pNumeric.matcher(curr).matches())) {
239: while (level < lastLevel--)
240: writer.writeEndElement();
241: lastNonValueLevel = level;
242: } else
243: level = lastNonValueLevel + 1;
244: if (curr.endsWith(":")) {
245: writer.writeStartElement(curr.substring(0,
246: curr.lastIndexOf(':')).trim().replaceAll(
247: "[ :]", "-"));
248: lastLevel = level + 1;
249: continue;
250: } else if (pColormap.matcher(curr).matches()) {
251: writer.writeStartElement(curr.substring(0,
252: curr.lastIndexOf(':')).trim().replaceAll(
253: "[ :]", "-"));
254: writer.writeAttribute("colors", curr.split(": ")[1]
255: .trim());
256: lastLevel = level + 1;
257: continue;
258: }
259: entry = curr.split(": ");
260: if (entry.length == 2) {
261: if (!valueEntry) {
262: writer.writeStartElement(entry[0].trim()
263: .replaceAll("[ :]", "-"));
264: writer.writeCharacters(entry[1]);
265: writer.writeEndElement();
266: } else {
267: writer.writeEmptyElement("value");
268: writer.writeAttribute("key", entry[0].trim()
269: .replaceAll("[ :]", "-"));
270: writer.writeAttribute("data", entry[1]);
271: // writer.writeEndElement();
272: }
273: } else {
274: // System.out.println("unknown line: "+curr);
275: }
276: lastLevel = level;
277: }
278: } catch (Exception e) {
279: LOG.error("Error at [" + curr + "]:" + e.getMessage());
280: }
281: writer.writeEndDocument();
282: writer.flush();
283: writer.close();
284: return sw.getBuffer().toString();
285: }
286:
287: }
|