Picture-Project 之 缩略图

照片项目 之 缩略图

Posted by John-zero on December 11, 2017

缩略图

单反拍摄高清无码照片大小3MB+, 导致 WEB 页面加载贼慢, 所以优化生成缩略图来代替.

使用的 Google Java Library:

<!-- https://github.com/coobird/thumbnailator -->
<!-- https://mvnrepository.com/artifact/net.coobird/thumbnailator -->
<dependency>
	<groupId>net.coobird</groupId>
	<artifactId>thumbnailator</artifactId>
	<version>0.4.8</version>
</dependency>

伪代码


// 照片常量类
public class PictureConstant
{
	
	// 缩略图标识
    public final static String THUMBNAIL = "thumbnail";

}

// 图片信息枚举
public enum PictureInfoEnum
{
	SIZE, // 大小
	WIDTH, // 宽度
	HEIGHT, // 高度
	;
}


//
// 以下是 PictureUtils 类的函数
//

import com.alibaba.fastjson.JSONObject;
import com.example.constant.PictureConstant;
import com.example.enums.PictureInfoEnum;
import com.google.common.io.Files;
import net.coobird.thumbnailator.Thumbnails;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by John_zero on 2017/12/10.
 */
public class PictureUtil
{
    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(PictureUtil.class);

    /**
     * 图片信息
     * @param imageFile
     * @return
     */
	public static Map<PictureInfoEnum, Object> imageInfo (String imagePath)
	{
		Map<PictureInfoEnum, Object> attribute = new HashMap<>();
		try
		{
			File imageFile = new File(imagePath);
			if(!imageFile.exists())
			{
				logger.warn(String.format("%s 图片文件不存在...", imagePath));
				return attribute;
			}
			if(imageFile.isDirectory())
			{
				logger.warn(String.format("%s 非图片文件...", imagePath));
				return attribute;
			}

			BufferedImage sourceImage = ImageIO.read(new FileInputStream(imageFile));

			double imageSize = imageFile.length() / 1024.0; // 源图大小
			attribute.put(PictureInfoEnum.SIZE, imageSize);
			logger.info(String.format("%s, 源图大小: %.1f", imagePath, imageSize));

			int imageWidth = sourceImage.getWidth(); // 源图宽度
			attribute.put(PictureInfoEnum.WIDTH, imageWidth);
			logger.info(String.format("%s, 源图宽度: %s", imagePath, imageWidth));

			int imageHeight = sourceImage.getHeight(); // 源图高度
			attribute.put(PictureInfoEnum.HEIGHT, imageHeight);
			logger.info(String.format("%s, 源图高度: %s", imagePath, imageHeight));
		}
		catch (Exception e)
		{
			logger.error("图片信息, 异常", e);
		}

		return attribute;
	}

    /**
     * 根据图片路径名称内部计算大小像素生成缩略图
     * @param sourcePath
     * @param targetPath
     * @return
     */
	public static JSONObject thumbnail (String sourcePath, String targetPath)
	{
		logger.info(String.format("源图片: %s, 目标缩略图: %s", sourcePath, targetPath));

		JSONObject jsonObject = new JSONObject();
		try
		{
			long startNano = System.nanoTime();

			File sourcePicture = new File(sourcePath);
			File targetPicture = new File(targetPath);

			Map<PictureInfoEnum, Object> attribute = imageInfo (sourcePicture);
			if(attribute == null || attribute.isEmpty())
			{
				jsonObject.put("code", "failure");
				jsonObject.put("message", String.format("%s 图片属性获取失败, 导致无法生成缩略图...", sourcePath));
				return jsonObject;
			}

			if(sourcePath.indexOf("_" + PictureConstant.THUMBNAIL) > -1) // 存在缩略图标识, 则直接返回图片全名称
			{
				return pictureCopy (sourcePicture, targetPicture);
			}

			double imageSize = (double) attribute.get(PictureInfoEnum.SIZE);
			if(imageSize < 1024.0D) // 图片大小小于阈值, 无须生成缩略图, 直接将原图拷贝作为缩略图
			{
				return pictureCopy (sourcePicture, targetPicture);
			}

			int imageWidth = (int) attribute.get(PictureInfoEnum.WIDTH);
			int imageHeight = (int) attribute.get(PictureInfoEnum.HEIGHT);

			int widthPixels, heightPixels;
			int maxCommonDivisor = AlgorithmUtil.maxCommonDivisor(imageWidth, imageHeight);
			if(maxCommonDivisor > 1) // 动态计算
			{
				widthPixels = imageWidth / maxCommonDivisor * 300;
				heightPixels = imageHeight / maxCommonDivisor * 300;

				// 如果动态计算后的像素大小超过原像素大小, 则保持原来的尺寸大小
				if(widthPixels > imageWidth)
					widthPixels = imageWidth;
				if(heightPixels > imageHeight)
					heightPixels = imageHeight;
			}
			else  // 原像素大小
			{
				widthPixels = imageWidth;
				heightPixels = imageHeight;
			}

			logger.info(String.format("根据图片路径名称 %s --> %s 内部计算缩略图大小像素: 宽: %s, 高: %s", sourcePath, targetPath, widthPixels, heightPixels));

			Thumbnails.of(sourcePicture).size(widthPixels, heightPixels).keepAspectRatio(true).toFile(targetPicture);

			long endNano = System.nanoTime();

			logger.info(String.format("根据图片路径名称 %s --> %s 内部计算大小像素生成缩略图耗时: %s", sourcePath, targetPath, (endNano - startNano) / 1000000));
		}
		catch (Exception e)
		{
			jsonObject.put("code", "exception");
			jsonObject.put("exception", e);

			logger.error("根据图片路径名称内部计算大小像素生成缩略图 异常", e);
		}

		return jsonObject;
	}

    /**
     * 图片拷贝
     * @param sourcePicture
     * @param targetPicture
     * @return
     */
	public static JSONObject pictureCopy (File sourcePicture, File targetPicture)
	{
		JSONObject jsonObject = new JSONObject();

		try
		{
			Files.copy(sourcePicture, targetPicture);
		}
		catch (Exception e)
		{
			logger.error(String.format("%s 直接拷贝作为缩略图...异常", sourcePicture.getPath()), e);

			jsonObject.put("code",  "exception");
			jsonObject.put("exception", e);

			return jsonObject;
		}

		jsonObject.put("code", "copy");
		jsonObject.put("message", String.format("%s 直接拷贝作为缩略图...", sourcePicture.getPath()));

		return jsonObject;
	}
	
}

注意点:

1. 生成缩略图非常耗时, 所以不建议频繁使用  

2. 如果由于 Thumbnails 使用不当而导致出现异常
	容易出现 java.lang.OutOfMemoryError: Java heap space 内存溢出

常见异常:

1. java.lang.NegativeArraySizeException: null
	at java.awt.image.DataBufferByte.<init>(DataBufferByte.java:76)
	at java.awt.image.Raster.createInterleavedRaster(Raster.java:266)
	at java.awt.image.BufferedImage.<init>(BufferedImage.java:376)
	at net.coobird.thumbnailator.builders.BufferedImageBuilder.build(Unknown Source)
	at net.coobird.thumbnailator.makers.ThumbnailMaker.makeThumbnail(Unknown Source)
	at net.coobird.thumbnailator.makers.FixedSizeThumbnailMaker.make(Unknown Source)
	at net.coobird.thumbnailator.Thumbnailator.createThumbnail(Unknown Source)
	at net.coobird.thumbnailator.Thumbnails$Builder.toFile(Unknown Source)	
	
这个大部分原因都是因为 width, height 设置不合理而导致的