001/*
002 * (C) Copyright 2006-2013 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Laurent Doguin
016 *     Florent Guillaume
017 */
018package org.nuxeo.ecm.platform.picture.core.im;
019
020import java.io.File;
021import java.io.IOException;
022
023import org.apache.commons.io.FilenameUtils;
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026import org.nuxeo.ecm.core.api.Blob;
027import org.nuxeo.ecm.core.api.Blobs;
028import org.nuxeo.ecm.platform.commandline.executor.api.CommandAvailability;
029import org.nuxeo.ecm.platform.commandline.executor.api.CommandException;
030import org.nuxeo.ecm.platform.commandline.executor.api.CommandLineExecutorService;
031import org.nuxeo.ecm.platform.commandline.executor.api.CommandNotAvailable;
032import org.nuxeo.ecm.platform.picture.core.ImageUtils;
033import org.nuxeo.ecm.platform.picture.magick.utils.ImageCropper;
034import org.nuxeo.ecm.platform.picture.magick.utils.ImageIdentifier;
035import org.nuxeo.ecm.platform.picture.magick.utils.ImageResizer;
036import org.nuxeo.ecm.platform.picture.magick.utils.ImageRotater;
037import org.nuxeo.runtime.api.Framework;
038
039public class IMImageUtils implements ImageUtils {
040
041    private static final Log log = LogFactory.getLog(IMImageUtils.class);
042
043    public static abstract class ImageMagickCaller {
044
045        protected File sourceFile;
046
047        // a tmp file is needed if the blob doesn't have a file, or
048        // if it has one but with an incorrect extension
049        protected File tmpFile;
050
051        protected File targetFile;
052
053        public Blob call(Blob blob, String targetExt, String commandName) {
054            CommandLineExecutorService cles = Framework.getLocalService(CommandLineExecutorService.class);
055            CommandAvailability availability = cles.getCommandAvailability(commandName);
056            if (!availability.isAvailable()) {
057                return null;
058            }
059
060            try {
061                makeFiles(blob, targetExt);
062
063                callImageMagick();
064
065                Blob targetBlob = Blobs.createBlob(targetFile);
066                Framework.trackFile(targetFile, targetBlob);
067                return targetBlob;
068            } catch (CommandNotAvailable | CommandException | IOException e) {
069                log.error("ImageMagick failed on command: " + commandName, e);
070                return null;
071            } finally {
072                if (tmpFile != null) {
073                    tmpFile.delete();
074                }
075            }
076        }
077
078        protected void makeFiles(Blob blob, String targetExt) throws CommandNotAvailable, CommandException, IOException {
079            sourceFile = blob.getFile();
080
081            // check extension
082            String ext = FilenameUtils.getExtension(blob.getFilename());
083            if (ext == null || "".equals(ext)) {
084                // no known extension
085                if (sourceFile == null) {
086                    sourceFile = createTempSource(blob, "tmp");
087                }
088                // detect extension
089                ext = ImageIdentifier.getInfo(sourceFile.getPath()).getFormat();
090                if (tmpFile == null) {
091                    // copy source with proper name
092                    sourceFile = createTempSource(blob, ext);
093                } else {
094                    // rename tmp file
095                    File newTmpFile = new File(FilenameUtils.removeExtension(tmpFile.getPath()) + "." + ext);
096                    tmpFile.renameTo(newTmpFile);
097                    tmpFile = newTmpFile;
098                    sourceFile = newTmpFile;
099                }
100            } else {
101                // check that extension on source is correct
102                if (sourceFile != null && !ext.equals(FilenameUtils.getExtension(sourceFile.getName()))) {
103                    sourceFile = null;
104                }
105            }
106
107            if (sourceFile == null) {
108                sourceFile = createTempSource(blob, ext);
109            }
110
111            if (targetExt == null) {
112                targetExt = ext;
113            }
114            targetFile = File.createTempFile("nuxeoImageTarget", "." + targetExt);
115        }
116
117        protected File createTempSource(Blob blob, String ext) throws IOException {
118            tmpFile = File.createTempFile("nuxeoImageSource", "." + ext);
119            blob.transferTo(tmpFile);
120            return tmpFile;
121        }
122
123        public abstract void callImageMagick() throws CommandNotAvailable, CommandException;
124    }
125
126    @Override
127    public Blob crop(Blob blob, final int x, final int y, final int width, final int height) {
128        return new ImageMagickCaller() {
129            @Override
130            public void callImageMagick() throws CommandNotAvailable, CommandException {
131                ImageCropper.crop(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath(), width, height, x, y);
132            }
133        }.call(blob, null, "resizer");
134    }
135
136    @Override
137    public Blob resize(Blob blob, String finalFormat, final int width, final int height, final int depth) {
138        return new ImageMagickCaller() {
139            @Override
140            public void callImageMagick() throws CommandNotAvailable, CommandException {
141                ImageResizer.resize(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath(), width, height, depth);
142            }
143        }.call(blob, finalFormat, "resizer");
144    }
145
146    @Override
147    public Blob rotate(Blob blob, final int angle) {
148        return new ImageMagickCaller() {
149            @Override
150            public void callImageMagick() throws CommandNotAvailable, CommandException {
151                ImageRotater.rotate(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath(), angle);
152            }
153        }.call(blob, null, "rotate");
154    }
155
156    @Override
157    public boolean isAvailable() {
158        CommandLineExecutorService cles = Framework.getLocalService(CommandLineExecutorService.class);
159        CommandAvailability commandAvailability = cles.getCommandAvailability("identify");
160        return commandAvailability.isAvailable();
161    }
162
163}