org.yes.cart.service.domain.impl.ImageServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.yes.cart.service.domain.impl.ImageServiceImpl.java

Source

/*
 * Copyright 2009 Denys Pavlov, Igor Azarnyi
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package org.yes.cart.service.domain.impl;

import org.apache.commons.lang.math.NumberUtils;
import org.hibernate.criterion.Restrictions;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.yes.cart.dao.GenericDAO;
import org.yes.cart.domain.entity.SeoImage;
import org.yes.cart.service.domain.ImageService;
import org.yes.cart.service.image.ImageNameStrategy;
import org.yes.cart.service.image.ImageNameStrategyResolver;
import org.yes.cart.stream.io.IOProvider;
import org.yes.cart.util.ShopCodeContext;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.Map;

/**
 * Image service to resize and store resized image.
 * <p/>
 * User: Igor Azarny iazarny@yahoo.com
 * Date: 09-May-2011
 * Time: 14:12:54
 */
public class ImageServiceImpl extends BaseGenericServiceImpl<SeoImage> implements ImageService {

    private final String allowedSizes;

    private final boolean cropToFit;
    private final int forceCropToFitOnSize;

    private final Color defaultBorder;
    private final Color alphaBorder = new Color(0, 0, 0, 0);

    private final ImageNameStrategyResolver imageNameStrategyResolver;

    private final GenericDAO<SeoImage, Long> seoImageDao;

    private final IOProvider ioProvider;

    /**
     * Construct image service.
     *
     * @param seoImageDao   image seo dao
     * @param imageNameStrategyResolver the image name strategy resolver
     * @param allowedSizes  sizes allowed for resizing
     * @param borderColorR  red border density
     * @param borderColorG  green border color density
     * @param borderColorB  blue border color density
     * @param cropToFit     setting this to true will crop the image to proper ratio
     *                      prior to scaling so that scaled image fills all the space.
     *                      This is useful for those who wish to have images that fill
     *                      all space dedicated for image without having border around
     *                      the image. For those who wish images of products in the middle
     * @param forceCropToFitOnSize forcefully use cropping if scale is below given size
     *                             This option is very useful for small thumbs (<100px)
     *                             as you really cannot see much with added padding for
     * @param ioProvider IO provider
     */
    public ImageServiceImpl(final GenericDAO<SeoImage, Long> seoImageDao,
            final ImageNameStrategyResolver imageNameStrategyResolver, final String allowedSizes,
            final int borderColorR, final int borderColorG, final int borderColorB, final boolean cropToFit,
            final int forceCropToFitOnSize, final IOProvider ioProvider) {

        super(seoImageDao);

        this.seoImageDao = seoImageDao;
        this.imageNameStrategyResolver = imageNameStrategyResolver;
        this.allowedSizes = allowedSizes;
        this.ioProvider = ioProvider;
        defaultBorder = new Color(borderColorR, borderColorG, borderColorB);

        this.cropToFit = cropToFit;
        this.forceCropToFitOnSize = forceCropToFitOnSize;
    }

    /**
     * {@inheritDoc}
     */
    public byte[] resizeImage(final String filename, final byte[] content, final String width,
            final String height) {
        return resizeImage(filename, content, width, height, cropToFit);
    }

    /**
     * {@inheritDoc}
     */
    public byte[] resizeImage(final String filename, final byte[] content, final String width, final String height,
            final boolean cropToFit) {

        try {

            final InputStream bis = new ByteArrayInputStream(content);

            final BufferedImage originalImg = ImageIO.read(bis);
            final String codec = getCodecFromFilename(filename);
            final boolean supportsAlpha = hasAlphaSupport(codec);

            int x = NumberUtils.toInt(width);
            int y = NumberUtils.toInt(height);
            int originalX = originalImg.getWidth();
            int originalY = originalImg.getHeight();

            boolean doCropToFit = cropToFit || x < forceCropToFitOnSize || y < forceCropToFitOnSize;

            final int imageType = originalImg.getType();

            final Image resizedImg;
            //            final BufferedImage resizedImg;
            final int padX, padY;
            if (doCropToFit) {
                // crop the original to best fit of target size
                int[] cropDims = cropImageToCenter(x, y, originalX, originalY);
                padX = 0;
                padY = 0;

                final BufferedImage croppedImg = originalImg.getSubimage(cropDims[0], cropDims[1], cropDims[2],
                        cropDims[3]);
                resizedImg = croppedImg.getScaledInstance(x, y, Image.SCALE_SMOOTH);

                //                final BufferedImage croppedImg = originalImg.getSubimage(cropDims[0], cropDims[1], cropDims[2], cropDims[3]);
                //                resizedImg = new BufferedImage(y, x, imageType);
                //                Graphics2D graphics = resizedImg.createGraphics();
                //                graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                //                graphics.drawImage(croppedImg, 0, 0, x, y, null);
            } else {
                int[] scaleDims = scaleImageToCenter(x, y, originalX, originalY);
                padX = scaleDims[0];
                padY = scaleDims[1];

                resizedImg = originalImg.getScaledInstance(scaleDims[2], scaleDims[3], Image.SCALE_SMOOTH);

                //                resizedImg = new BufferedImage(scaleDims[3], scaleDims[2], imageType);
                //                Graphics2D graphics = resizedImg.createGraphics();
                //                graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                //                graphics.drawImage(originalImg, 0, 0, scaleDims[2], scaleDims[3], null);
            }

            // base canvas
            final BufferedImage resizedImgFinal = new BufferedImage(x, y, imageType);

            // fill base color
            final Graphics2D graphics = resizedImgFinal.createGraphics();
            graphics.setPaint(supportsAlpha ? alphaBorder : defaultBorder);
            graphics.fillRect(0, 0, x, y);

            // insert scaled image
            graphics.drawImage(resizedImg, padX, padY, null);

            final ByteArrayOutputStream bos = new ByteArrayOutputStream();

            ImageIO.write(resizedImgFinal, codec, bos);

            return bos.toByteArray();

        } catch (Exception exp) {
            ShopCodeContext.getLog(this).error("Unable to resize image " + filename, exp);
        }

        return new byte[0];

    }

    /**
     * Get the image codec from filename's given extension
     * <p/>
     * FIXME: This is a very primitive check - need something more robust, MIME Types maybe?
     *
     * @param filename file name
     * @return codec name
     */
    private String getCodecFromFilename(final String filename) {
        final String fileExt = filename.substring(filename.lastIndexOf('.') + 1);
        final String ext = fileExt.toLowerCase();
        if ("jpg".equals(ext)) {
            return "jpeg";
        } else if ("tif".equals(ext)) {
            return "tiff";
        }
        return ext;
    }

    /**
     * Check if image codec supports alpha channel.
     *
     * FIXME: simple check based on codec name
     *
     * @param codec code
     * @return true if alpha is supported
     */
    private boolean hasAlphaSupport(final String codec) {
        return "png".equals(codec);
    }

    int[] cropImageToCenter(final int targetX, final int targetY, final int originalX, final int originalY) {

        // calculate crop so that we can scale to fit
        BigDecimal sourceRatio = new BigDecimal(originalX).divide(new BigDecimal(originalY), 10,
                BigDecimal.ROUND_HALF_UP);
        BigDecimal targetRatio = new BigDecimal(targetX).divide(new BigDecimal(targetY), 10,
                BigDecimal.ROUND_HALF_UP);
        final int cropWidth, cropHeight, cropX, cropY;
        if (sourceRatio.compareTo(targetRatio) < 0) { // need to crop by height
            cropWidth = originalX;
            cropHeight = new BigDecimal(originalY)
                    .divide((targetRatio.divide(sourceRatio, 10, BigDecimal.ROUND_HALF_UP)), 0,
                            BigDecimal.ROUND_HALF_UP)
                    .intValue();
            cropX = 0;
            cropY = (originalY - cropHeight) / 2;
        } else { // need to crop by width
            cropHeight = originalY;
            cropWidth = new BigDecimal(originalX)
                    .divide((sourceRatio.divide(targetRatio, 10, BigDecimal.ROUND_HALF_UP)), 0,
                            BigDecimal.ROUND_HALF_UP)
                    .intValue();
            cropX = (originalX - cropWidth) / 2;
            cropY = 0;
        }

        return new int[] { cropX, cropY, cropWidth, cropHeight };

    }

    int[] scaleImageToCenter(final int targetX, final int targetY, final int originalX, final int originalY) {

        final BigDecimal xScale = new BigDecimal(targetX).divide(new BigDecimal(originalX), 10,
                BigDecimal.ROUND_HALF_UP);
        final BigDecimal yScale = new BigDecimal(targetY).divide(new BigDecimal(originalY), 10,
                BigDecimal.ROUND_HALF_UP);

        final int scaleWidth, scaleHeight, padX, padY;
        if (xScale.compareTo(yScale) < 0) { // need to scale by height
            scaleWidth = targetX;
            scaleHeight = new BigDecimal(originalY).multiply(xScale).setScale(0, BigDecimal.ROUND_HALF_UP)
                    .intValue();
            padX = 0;
            padY = (targetY - scaleHeight) / 2;
        } else { // need to scale by width
            scaleHeight = targetY;
            scaleWidth = new BigDecimal(originalX).multiply(yScale).setScale(0, BigDecimal.ROUND_HALF_UP)
                    .intValue();
            padX = (targetX - scaleWidth) / 2;
            padY = 0;
        }

        return new int[] { padX, padY, scaleWidth, scaleHeight };

    }

    /**
     * {@inheritDoc}
     */
    public boolean isSizeAllowed(final String size) {
        return allowedSizes.contains(size);
    }

    /**
     * {@inheritDoc}
     */
    public boolean isSizeAllowed(final String width, final String height) {
        return isSizeAllowed(width + "x" + height);
    }

    /** {@inheritDoc} */
    public ImageNameStrategy getImageNameStrategy(final String url) {
        return imageNameStrategyResolver.getImageNameStrategy(url);
    }

    /** {@inheritDoc} */
    public byte[] resizeImage(final String original, final String resized, final String width,
            final String height) {
        return resizeImage(original, resized, width, height, cropToFit);
    }

    /** {@inheritDoc} */
    public byte[] resizeImage(final String original, final String resized, final String width, final String height,
            final boolean cropToFit) {

        try {
            final Map<String, Object> ctx = Collections.EMPTY_MAP;

            if (resized != null) {

                final boolean resizedIsNewer = ioProvider.isNewerThan(resized, original, ctx);

                if (!resizedIsNewer) {

                    final byte[] originalContent = ioProvider.read(original, ctx);
                    final byte[] resizedContent = resizeImage(original, originalContent, width, height);

                    if (resizedContent.length > 0) {
                        ioProvider.write(resized, resizedContent, ctx);
                    }

                    return resizedContent;
                }

                return ioProvider.read(resized, ctx);
            }
            return ioProvider.read(original, ctx);

        } catch (IOException ioe) {
            ShopCodeContext.getLog(this).error("Unable to resize image {} to {}", original, resized);
            ShopCodeContext.getLog(this).error(ioe.getMessage(), ioe);
            return new byte[0];
        }
    }

    /** {@inheritDoc} */
    public boolean isImageInRepository(final String fullFileName, final String code, final String storagePrefix,
            final String pathToRepository) {

        final ImageNameStrategy strategy = getImageNameStrategy(storagePrefix);
        final String filename = strategy.resolveFileName(fullFileName);

        String pathInRepository = pathToRepository
                + strategy.resolveRelativeInternalFileNamePath(filename, code, null);

        return ioProvider.exists(pathInRepository, Collections.EMPTY_MAP);

    }

    /** {@inheritDoc} */
    public String addImageToRepository(final String fullFileName, final String code, final byte[] imgBody,
            final String storagePrefix, final String pathToRepository) throws IOException {

        final ImageNameStrategy strategy = getImageNameStrategy(storagePrefix);
        final String filename = strategy.resolveFileName(fullFileName);
        final String suffix = strategy.resolveSuffix(fullFileName);
        final String locale = strategy.resolveLocale(fullFileName);

        String pathInRepository = pathToRepository
                + strategy.resolveRelativeInternalFileNamePath(filename, code, null);

        final String uniqueName = createRepositoryUniqueName(pathInRepository, ioProvider, strategy, code, suffix,
                locale);

        ioProvider.write(uniqueName, imgBody, Collections.EMPTY_MAP);

        return strategy.resolveFileName(uniqueName);
    }

    /*
     * Check is given file name present on disk and create new if necessary.
     * Return sequential file name.
     */
    String createRepositoryUniqueName(final String fileName, final IOProvider ioProvider,
            final ImageNameStrategy strategy, final String code, final String suffix, final String locale) {
        if (ioProvider.exists(fileName, Collections.EMPTY_MAP)) {
            final String newFileName = strategy.createRollingFileName(fileName, code, suffix, locale);
            return createRepositoryUniqueName(newFileName, ioProvider, strategy, code, suffix, locale);
        }
        return fileName;
    }

    /** {@inheritDoc} */
    public byte[] imageToByteArray(final String fileName, final String code, final String storagePrefix,
            final String pathToRepository) throws IOException {

        final ImageNameStrategy strategy = getImageNameStrategy(storagePrefix);
        final String file = strategy.resolveFileName(fileName);
        String pathInRepository = pathToRepository + strategy.resolveRelativeInternalFileNamePath(file, code, null);

        return ioProvider.read(pathInRepository, Collections.EMPTY_MAP);

    }

    /** {@inheritDoc}*/
    public boolean deleteImage(final String imageFileName, final String storagePrefix,
            final String pathToRepository) {

        final SeoImage seoImage = getSeoImage(storagePrefix + imageFileName);
        if (seoImage != null) {
            getGenericDao().delete(seoImage);
        }

        final ImageNameStrategy strategy = getImageNameStrategy(storagePrefix);
        final String file = strategy.resolveFileName(imageFileName);
        final String code = strategy.resolveObjectCode(imageFileName);
        String pathInRepository = pathToRepository + strategy.resolveRelativeInternalFileNamePath(file, code, null);

        try {
            ioProvider.delete(pathInRepository, Collections.EMPTY_MAP);
            return true;
        } catch (IOException e) {
            return false;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Cacheable(value = "imageService-seoImage")
    public SeoImage getSeoImage(final String imageName) {
        java.util.List<SeoImage> seoImages = seoImageDao.findByCriteria(Restrictions.eq("imageName", imageName));
        if (seoImages == null || seoImages.isEmpty()) {
            return null;
        }
        return seoImages.get(0);
    }

    /**
     * {@inheritDoc}
     */
    @CacheEvict(value = { "imageService-seoImage" }, allEntries = true)
    public SeoImage update(SeoImage instance) {
        return super.update(instance);
    }

    /**
     * {@inheritDoc}
     */
    @CacheEvict(value = { "imageService-seoImage" }, allEntries = true)
    public void delete(SeoImage instance) {
        super.delete(instance);
    }
}