Package ch.entwine.weblounge.preview.imagemagick

Source Code of ch.entwine.weblounge.preview.imagemagick.ImageMagickPreviewGenerator

/*
*  Weblounge: Web Content Management System
*  Copyright (c) 2003 - 2011 The Weblounge Team
*  http://entwinemedia.com/weblounge
*
*  This program is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public License
*  as published by the Free Software Foundation; either version 2
*  of the License, or (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public License
*  along with this program; if not, write to the Free Software Foundation
*  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package ch.entwine.weblounge.preview.imagemagick;

import ch.entwine.weblounge.common.content.Resource;
import ch.entwine.weblounge.common.content.ResourceContent;
import ch.entwine.weblounge.common.content.image.ImagePreviewGenerator;
import ch.entwine.weblounge.common.content.image.ImageResource;
import ch.entwine.weblounge.common.content.image.ImageStyle;
import ch.entwine.weblounge.common.impl.content.image.ImageStyleUtils;
import ch.entwine.weblounge.common.language.Language;
import ch.entwine.weblounge.common.site.Environment;
import ch.entwine.weblounge.common.site.ImageScalingMode;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.im4java.core.ConvertCmd;
import org.im4java.core.IMOperation;
import org.im4java.core.Info;
import org.im4java.process.OutputConsumer;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Utility class used for dealing with images and image styles.
*/
public final class ImageMagickPreviewGenerator implements ImagePreviewGenerator {

  /** The logging facility */
  private static final Logger logger = LoggerFactory.getLogger(ImageMagickPreviewGenerator.class);

  /** List of supported formats (cached) */
  private final Map<String, Boolean> supportedFormats = new HashMap<String, Boolean>();

  /** Flag to indicate whether format detection is supported */
  private boolean formatDecetionSupported = true;

  /** The image magic temp directory */
  private File imageMagickDir = null;

  /**
   * Called by the {@link ImageMagickActivator} on service activation.
   *
   * @param ctx
   *          the component context
   */
  void activate(ComponentContext ctx) {
    try {
      prepareDirectory();
    } catch (IOException e) {
      throw new IllegalStateException(e);
    }
  }

  /**
   * Called by the {@link ImageMagickActivator} on service inactivation.
   */
  void deactivate() {
    FileUtils.deleteQuietly(imageMagickDir);
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.content.PreviewGenerator#supports(ch.entwine.weblounge.common.content.Resource)
   */
  public boolean supports(Resource<?> resource) {
    return (resource instanceof ImageResource);
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.content.PreviewGenerator#supports(java.lang.String)
   */
  public boolean supports(String format) {
    if (format == null)
      throw new IllegalArgumentException("Format cannot be null");

    if (!formatDecetionSupported)
      return true;

    // Check for verified support
    if (supportedFormats.containsKey(format))
      return supportedFormats.get(format);

    // Reach out to ImageMagick
    ConvertCmd imageMagick = new ConvertCmd();
    IMOperation op = new IMOperation();
    op.identify().list("format");
    try {
      final Pattern p = Pattern.compile("[\\s]+" + format.toUpperCase() + "[\\s]+rw");
      final Boolean[] supported = new Boolean[1];
      imageMagick.setOutputConsumer(new OutputConsumer() {
        public void consumeOutput(InputStream is) throws IOException {
          String output = IOUtils.toString(is);
          Matcher m = p.matcher(output);
          supported[0] = new Boolean(m.find());
        }
      });
      imageMagick.run(op);

      // Cache the result
      supportedFormats.put(format, supported[0]);

      return supported[0];
    } catch (Throwable t) {
      logger.warn("Error looking up formats supported by ImageMagick: {}", t.getMessage());
      formatDecetionSupported = false;
      logger.info("ImageMagick format lookup failed, assuming support for all formats");
      return true;
    }
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.content.PreviewGenerator#createPreview(ch.entwine.weblounge.common.content.Resource,
   *      ch.entwine.weblounge.common.site.Environment,
   *      ch.entwine.weblounge.common.language.Language,
   *      ch.entwine.weblounge.common.content.image.ImageStyle, String,
   *      java.io.InputStream, java.io.OutputStream)
   */
  public void createPreview(Resource<?> resource, Environment environment,
      Language language, ImageStyle style, String format, InputStream is,
      OutputStream os) throws IOException {

    if (format == null) {
      if (resource == null)
        throw new IllegalArgumentException("Resource cannot be null");
      if (resource.getContent(language) == null) {
        logger.warn("Skipping creation of preview for {} in language '{}': no content", resource, language.getIdentifier());
        return;
      }
      String mimetype = resource.getContent(language).getMimetype();
      logger.debug("Image preview of {} is generated using the resource's mimetype '{}'", resource.getIdentifier(), mimetype);
      format = mimetype.substring(mimetype.indexOf("/") + 1);
    }
    style(is, os, format, style);
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.content.image.ImagePreviewGenerator#createPreview(java.io.File,
   *      ch.entwine.weblounge.common.site.Environment,
   *      ch.entwine.weblounge.common.language.Language,
   *      ch.entwine.weblounge.common.content.image.ImageStyle,
   *      java.lang.String, java.io.InputStream, java.io.OutputStream)
   */
  public void createPreview(File imageFile, Environment environment,
      Language language, ImageStyle style, String format, InputStream is,
      OutputStream os) throws IOException {

    if (format == null) {
      if (imageFile == null)
        throw new IllegalArgumentException("Image file cannot be null");
      format = FilenameUtils.getExtension(imageFile.getName());
      logger.debug("Image preview of {} is generated as '{}'", imageFile.getAbsolutePath(), format);
    }

    style(is, os, format, style);
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.content.PreviewGenerator#getContentType(ch.entwine.weblounge.common.content.Resource,
   *      ch.entwine.weblounge.common.language.Language,
   *      ch.entwine.weblounge.common.content.image.ImageStyle)
   */
  public String getContentType(Resource<?> resource, Language language,
      ImageStyle style) {
    String mimetype = resource.getContent(language).getMimetype();
    return mimetype;
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.content.PreviewGenerator#getSuffix(ch.entwine.weblounge.common.content.Resource,
   *      ch.entwine.weblounge.common.language.Language,
   *      ch.entwine.weblounge.common.content.image.ImageStyle)
   */
  public String getSuffix(Resource<?> resource, Language language,
      ImageStyle style) {
    // Load the resource
    ResourceContent content = resource.getContent(language);
    if (content == null) {
      content = resource.getOriginalContent();
      if (content == null) {
        logger.warn("Trying to get filename suffix for {}, which has no content", resource);
        return null;
      }
    }

    // Get the file name
    String filename = content.getFilename();
    if (StringUtils.isBlank(filename)) {
      logger.warn("Trying to get filename suffix for {}, which has no filename", resource);
      return null;
    }

    // Add the file identifier name
    if (StringUtils.isNotBlank(style.getIdentifier())) {
      filename += "-" + style.getIdentifier();
    }

    return FilenameUtils.getExtension(filename);
  }

  /**
   * {@inheritDoc}
   *
   * @see ch.entwine.weblounge.common.content.PreviewGenerator#getPriority()
   */
  public int getPriority() {
    return 100;
  }

  /**
   * Resizes the given image to what is defined by the image style and writes
   * the result to the output stream.
   *
   * @param is
   *          the input stream
   * @param os
   *          the output stream
   * @param format
   *          the image format
   * @param style
   *          the style
   * @throws IllegalArgumentException
   *           if the image is in an unsupported format
   * @throws IllegalArgumentException
   *           if the input stream is empty
   * @throws IOException
   *           if reading from or writing to the stream fails
   * @throws OutOfMemoryError
   *           if the image is too large to be processed in memory
   */
  @SuppressWarnings("cast")
  private void style(InputStream is, OutputStream os, String format,
      ImageStyle style) throws IllegalArgumentException, IOException,
      OutOfMemoryError {

    // Does the input stream contain any data?
    if (is.available() == 0)
      throw new IllegalArgumentException("Empty input stream was passed to image styling");

    // Do we need to do any work at all?
    if (style == null || ImageScalingMode.None.equals(style.getScalingMode())) {
      logger.trace("No scaling needed, performing a noop stream copy");
      IOUtils.copy(is, os);
      return;
    }

    String uuid = UUID.randomUUID().toString();
    File originalFile = new File(imageMagickDir, "image-" + uuid + "." + format);
    File scaledFile = new File(imageMagickDir, "image-scaled-" + uuid + "." + format);
    File croppedFile = new File(imageMagickDir, "image-cropped-" + uuid + "." + format);

    try {

      File finalFile = null;
      FileOutputStream fos = new FileOutputStream(originalFile);
      IOUtils.copy(is, fos);
      IOUtils.closeQuietly(fos);
      IOUtils.closeQuietly(is);

      // Load the image from the temporary file
      Info imageInfo = new Info(originalFile.getAbsolutePath(), true);

      // Get the original image size
      int imageWidth = imageInfo.getImageWidth();
      int imageHeight = imageInfo.getImageHeight();

      // Prepare for processing
      ConvertCmd imageMagick = new ConvertCmd();

      // Resizing
      float scale = ImageStyleUtils.getScale(imageWidth, imageHeight, style);

      int scaledWidth = Math.round(scale * imageWidth);
      int scaledHeight = Math.round(scale * imageHeight);
      int cropX = 0;
      int cropY = 0;

      // If either one of scaledWidth or scaledHeight is < 1.0, then
      // the scale needs to be adapted to scale to 1.0 exactly and accomplish
      // the rest by cropping.

      if (scaledWidth < 1.0f) {
        scale = 1.0f / imageWidth;
        scaledWidth = 1;
        cropY = imageHeight - scaledHeight;
        scaledHeight = Math.round(imageHeight * scale);
      } else if (scaledHeight < 1.0f) {
        scale = 1.0f / imageHeight;
        scaledHeight = 1;
        cropX = imageWidth - scaledWidth;
        scaledWidth = Math.round(imageWidth * scale);
      }

      // Do the scaling
      IMOperation scaleOp = new IMOperation();
      scaleOp.addImage(originalFile.getAbsolutePath());
      scaleOp.resize((int) scaledWidth, (int) scaledHeight);
      scaleOp.addImage(scaledFile.getAbsolutePath());
      imageMagick.run(scaleOp);
      finalFile = scaledFile;

      // Cropping
      cropX = (int) Math.max(cropX, Math.ceil(ImageStyleUtils.getCropX(scaledWidth, scaledHeight, style)));
      cropY = (int) Math.max(cropY, Math.ceil(ImageStyleUtils.getCropY(scaledWidth, scaledHeight, style)));

      if ((cropX > 0 && Math.floor(cropX / 2.0f) > 0) || (cropY > 0 && Math.floor(cropY / 2.0f) > 0)) {

        int croppedLeft = (int) (cropX > 0 ? ((float) Math.floor(cropX / 2.0f)) : 0.0f);
        int croppedTop = (int) (cropY > 0 ? ((float) Math.floor(cropY / 2.0f)) : 0.0f);
        int croppedWidth = (int) (scaledWidth - Math.max(cropX, 0.0f));
        int croppedHeight = (int) (scaledHeight - Math.max(cropY, 0.0f));

        // Do the cropping
        IMOperation cropOperation = new IMOperation();
        cropOperation.addImage(scaledFile.getAbsolutePath());
        cropOperation.crop(croppedWidth, croppedHeight, croppedLeft, croppedTop);
        cropOperation.p_repage(); // Reset the page canvas and position to match
        // the actual cropped image
        cropOperation.addImage(croppedFile.getAbsolutePath());
        imageMagick.run(cropOperation);
        finalFile = croppedFile;
      }

      // Write resized/cropped image encoded as JPEG to the output stream
      FileInputStream fis = null;
      try {
        fis = new FileInputStream(finalFile);
        IOUtils.copy(fis, os);
      } finally {
        IOUtils.closeQuietly(fis);
      }

    } catch (Throwable t) {
      throw new IllegalArgumentException(t.getMessage());
    } finally {
      FileUtils.deleteQuietly(originalFile);
      FileUtils.deleteQuietly(scaledFile);
      FileUtils.deleteQuietly(croppedFile);
    }
  }

  /**
   * Makes sure that a temp directory exists.
   *
   * @throws IOException
   *           if the directory cannot be created
   */
  private void prepareDirectory() throws IOException {
    imageMagickDir = new File(FileUtils.getTempDirectory(), "imagemagick");
    if (!imageMagickDir.isDirectory() && !imageMagickDir.mkdirs()) {
      logger.error("Unable to create temp directory for ImageMagick at {}", imageMagickDir);
      throw new IOException("Unable to create temp directory for ImageMagick at " + imageMagickDir);
    }

  }

}
TOP

Related Classes of ch.entwine.weblounge.preview.imagemagick.ImageMagickPreviewGenerator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.