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