org.nervousync.utils.ImageUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of utils-jdk11 Show documentation
Show all versions of utils-jdk11 Show documentation
Java utility collections, development by Nervousync Studio (NSYC)
/*
* Licensed to the Nervousync Studio (NSYC) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.nervousync.utils;
import java.awt.AlphaComposite;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Optional;
import javax.imageio.ImageIO;
import org.nervousync.beans.image.CutOptions;
import org.nervousync.beans.image.MarkOptions;
import org.nervousync.commons.Globals;
/**
* Image Utilities
* 图片工具集
*
* @author Steven Wee [email protected]
* @version $Revision: 1.2.0 $ $Date: May 1, 2018 13:49:46 $
*/
public final class ImageUtils {
/**
* Logger instance
* 日志实例
*/
private static final LoggerUtils.Logger LOGGER = LoggerUtils.getLogger(ImageUtils.class);
/**
* Private constructor for ImageUtils
* 图片工具集的私有构造方法
*/
private ImageUtils() {
}
/**
* Retrieve image width value
* 获取图片宽度
*
* @param imagePath Image file path
* 图片地址
*
* @return Image width value
* 图片宽度值
*/
public static int imageWidth(final String imagePath) {
if (FileUtils.isExists(imagePath) && FileUtils.imageFile(imagePath)) {
try {
BufferedImage srcImage = ImageIO.read(FileUtils.getFile(imagePath));
return srcImage.getWidth(null);
} catch (Exception e) {
LOGGER.error("Read_Image_Error");
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Stack_Message_Error", e);
}
}
}
return Globals.DEFAULT_VALUE_INT;
}
/**
* Retrieve image height value
* 获取图片高度
*
* @param imagePath Image file path
* 图片地址
*
* @return Image height value
* 图片高度值
*/
public static int imageHeight(final String imagePath) {
if (FileUtils.isExists(imagePath) && FileUtils.imageFile(imagePath)) {
try {
BufferedImage srcImage = ImageIO.read(FileUtils.getFile(imagePath));
return srcImage.getHeight(null);
} catch (Exception e) {
LOGGER.error("Read_Image_Error");
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Stack_Message_Error", e);
}
}
}
return Globals.DEFAULT_VALUE_INT;
}
/**
* Retrieve image ratio value
* 获取图片宽高比
*
* @param imagePath Image file path
* 图片地址
*
* @return Image ratio value
* 图片宽高比
*/
public static double imageRatio(final String imagePath) {
double imageHeight = ImageUtils.imageHeight(imagePath);
double imageWidth = ImageUtils.imageHeight(imagePath);
if (imageHeight == Globals.DEFAULT_VALUE_DOUBLE || imageWidth == Globals.DEFAULT_VALUE_DOUBLE) {
return Globals.DEFAULT_VALUE_DOUBLE;
}
return imageWidth / imageHeight;
}
/**
* Cut original image file and save to target path by given cut options
* 根据给定的切割参数对原始图片进行切割并存储到目标地址
*
* @param origPath original image file path
* 原始图片地址
* @param targetPath target image file path
* 目标图片地址
* @param cutOptions cut options
* 切割参数
*
* @return Cut process result
* 切割处理结果
*/
public static boolean cutImage(final String origPath, final String targetPath, final CutOptions cutOptions) {
if (origPath != null && FileUtils.isExists(origPath) && cutOptions != null) {
if (cutOptions.getPositionX() + cutOptions.getCutWidth() > ImageUtils.imageWidth(origPath)) {
LOGGER.error("Width_Exceeds_Original_Image_Error");
return Boolean.FALSE;
}
if (cutOptions.getPositionY() + cutOptions.getCutHeight() > ImageUtils.imageHeight(origPath)) {
LOGGER.error("Height_Exceeds_Original_Image_Error");
return Boolean.FALSE;
}
try {
BufferedImage srcImage = ImageIO.read(FileUtils.getFile(origPath));
BufferedImage bufferedImage =
new BufferedImage(cutOptions.getCutWidth(), cutOptions.getCutHeight(),
BufferedImage.TYPE_INT_RGB);
for (int i = 0 ; i < cutOptions.getCutWidth() ; i++) {
for (int j = 0 ; j < cutOptions.getCutHeight() ; j++) {
bufferedImage.setRGB(i, j,
srcImage.getRGB(cutOptions.getPositionX() + i, cutOptions.getPositionY() + j));
}
}
return ImageIO.write(bufferedImage, StringUtils.getFilenameExtension(targetPath),
FileUtils.getFile(targetPath));
} catch (Exception e) {
LOGGER.error("Cut_Image_Error");
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Stack_Message_Error", e);
}
}
}
return Boolean.FALSE;
}
/**
* Resize original image file and save to target path by given ratio value
* 根据给定的缩放比例对原始图片进行放大/缩小并存储到目标地址
*
* @param origPath original image file path
* 原始图片地址
* @param targetPath target image file path
* 目标图片地址
* @param ratio ratio value
* 缩放比例
*
* @return Resize process result
* 修改尺寸处理结果
*/
public static boolean resizeByRatio(final String origPath, final String targetPath, final double ratio) {
return ImageUtils.resizeByRatio(origPath, targetPath, ratio, null);
}
/**
* Resize original image file and save to target path by given ratio value, and add mark to target image if configured
* 根据给定的缩放比例对原始图片进行放大/缩小并存储到目标地址,并添加水印到目标图片(如果设置了水印选项)
*
* @param origPath original image file path
* 原始图片地址
* @param targetPath target image file path
* 目标图片地址
* @param ratio ratio value
* 缩放比例
* @param markOptions Mark options
* 水印选项
*
* @return Resize process result
* 修改尺寸处理结果
*/
public static boolean resizeByRatio(final String origPath, final String targetPath, final double ratio,
final MarkOptions markOptions) {
if (FileUtils.isExists(origPath) && FileUtils.imageFile(origPath) && ratio > 0) {
try {
BufferedImage srcImage = ImageIO.read(FileUtils.getFile(origPath));
int origWidth = srcImage.getWidth(null);
int origHeight = srcImage.getHeight(null);
int targetWidth = Double.valueOf(origWidth * ratio).intValue();
int targetHeight = Double.valueOf(origHeight * ratio).intValue();
return ImageIO.write(processImage(srcImage, targetWidth, targetHeight, markOptions),
StringUtils.getFilenameExtension(targetPath),
FileUtils.getFile(targetPath));
} catch (Exception e) {
LOGGER.error("Resize_Image_Error");
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Stack_Message_Error", e);
}
}
}
return Boolean.FALSE;
}
/**
* Resize original image file to the given width and height, save to target path
* 将原始图片调整到指定的宽度、高度,并存储到目标地址
*
* @param origPath original image file path
* 原始图片地址
* @param targetPath target image file path
* 目标图片地址
* @param targetWidth target width (if -1 width will auto set by height ratio)
* 图片调整后的宽度,如果值为-1则自动根据图片宽高比进行调整
* @param targetHeight target height (if -1 height will auto set by width ratio)
* 图片调整后的高度,如果值为-1则自动根据图片宽高比进行调整
*
* @return Resize process result
* 修改尺寸处理结果
*/
public static boolean resizeTo(final String origPath, final String targetPath,
final int targetWidth, final int targetHeight) {
return ImageUtils.resizeTo(origPath, targetPath, targetWidth, targetHeight, null);
}
/**
* Resize original image file to the given width and height, save to target path, and add mark to target image if configured
* 将原始图片调整到指定的宽度、高度,并存储到目标地址,并添加水印到目标图片(如果设置了水印选项)
*
* @param origPath original image file path
* 原始图片地址
* @param targetPath target image file path
* 目标图片地址
* @param targetWidth target width (if -1 width will auto set by height ratio)
* 图片调整后的宽度,如果值为-1则自动根据图片宽高比进行调整
* @param targetHeight target height (if -1 height will auto set by width ratio)
* 图片调整后的高度,如果值为-1则自动根据图片宽高比进行调整
* @param markOptions Mark options
* 水印选项
*
* @return Resize process result
* 修改尺寸处理结果
*/
public static boolean resizeTo(final String origPath, final String targetPath,
final int targetWidth, final int targetHeight, final MarkOptions markOptions) {
if (FileUtils.isExists(origPath) && FileUtils.imageFile(origPath)
&& (targetWidth > 0 || targetHeight > 0)) {
try {
BufferedImage srcImage = ImageIO.read(FileUtils.getFile(origPath));
int origWidth = srcImage.getWidth(null);
int origHeight = srcImage.getHeight(null);
int resizeWidth;
if (targetWidth == Globals.DEFAULT_VALUE_INT) {
double ratio = targetHeight * 1.0 / origHeight;
resizeWidth = Double.valueOf(ratio * origWidth).intValue();
} else {
resizeWidth = targetWidth;
}
int resizeHeight;
if (targetHeight == Globals.DEFAULT_VALUE_INT) {
double ratio = targetWidth * 1.0 / origWidth;
resizeHeight = Double.valueOf(ratio * origHeight).intValue();
} else {
resizeHeight = targetHeight;
}
return ImageIO.write(processImage(srcImage, resizeWidth, resizeHeight, markOptions),
StringUtils.getFilenameExtension(targetPath), FileUtils.getFile(targetPath));
} catch (Exception e) {
LOGGER.error("Resize_Image_Error");
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Stack_Message_Error", e);
}
}
}
return Boolean.FALSE;
}
/**
* Add mark to original image and save result image to target path
* 添加水印到原始图片,并将添加好水印的图片保存到目标地址
*
* @param origPath original image file path
* 原始图片地址
* @param targetPath target image file path
* 目标图片地址
* @param markOptions Mark options
* 水印选项
*
* @return Mark process result
* 添加水印处理结果
*/
public static boolean markImage(final String origPath, final String targetPath, final MarkOptions markOptions) {
int imageWidth = ImageUtils.imageWidth(origPath);
int imageHeight = ImageUtils.imageHeight(origPath);
try {
BufferedImage bufferedImage = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
return ImageIO.write(processImage(bufferedImage, imageWidth, imageHeight, markOptions),
StringUtils.getFilenameExtension(targetPath),
FileUtils.getFile(targetPath));
} catch (Exception e) {
LOGGER.error("Water_Mark_Image_Error");
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Stack_Message_Error", e);
}
}
return Boolean.FALSE;
}
/**
* Calculate dHash hamming between original image and target image
* 计算原始图片和目标图片间差异值哈希的汉明距离
*
* @param origPath original image file path
* 原始图片地址
* @param targetPath target image file path
* 目标图片地址
*
* @return Calculated hamming result
* 计算的汉明距离
*/
public static int dHashHamming(final String origPath, final String targetPath) {
String origHash = ImageUtils.dHash(origPath);
String destHash = ImageUtils.dHash(targetPath);
int diff = 0;
for (int j = 0 ; j < origHash.length() ; j++) {
diff += (origHash.charAt(j) ^ destHash.charAt(j));
}
return diff;
}
/**
* Calculate pHash hamming between original image and target image
* 计算原始图片和目标图片间感知哈希的汉明距离
*
* @param origPath original image file path
* 原始图片地址
* @param targetPath target image file path
* 目标图片地址
*
* @return Calculated hamming result
* 计算的汉明距离
*/
public static int pHashHamming(final String origPath, final String targetPath) {
String origHash = ImageUtils.pHash(origPath);
String destHash = ImageUtils.pHash(targetPath);
int diff = 0;
for (int j = 0 ; j < origHash.length() ; j++) {
diff += (origHash.charAt(j) ^ destHash.charAt(j));
}
return diff;
}
/**
* Calculate dHash of given image file
* 计算给定图片的差异值哈希
*
* @param filePath Image file path
* 图片文件地址
*
* @return dHash string
* 差异值哈希字符串
*/
public static String dHash(final String filePath) {
try {
return ImageUtils.dHash(FileUtils.getFile(filePath));
} catch (FileNotFoundException e) {
LOGGER.error("Not_Found_File_Error", filePath);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Stack_Message_Error", e);
}
return Globals.DEFAULT_VALUE_STRING;
}
}
/**
* Calculate dHash of given image file
* 计算给定图片的差异值哈希
*
* @param file Image file instance
* 图片文件实例对象
*
* @return dHash string
* 差异值哈希字符串
*/
public static String dHash(final File file) {
try {
return ImageUtils.dHash(ImageIO.read(file));
} catch (IOException e) {
LOGGER.error("Read_Files_Error");
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Stack_Message_Error", e);
}
return Globals.DEFAULT_VALUE_STRING;
}
}
/**
* Calculate dHash of given image file
* 计算给定图片的差异值哈希
*
* @param bufferedImage Buffered image
* 缓冲图片实例对象
*
* @return dHash string
* 差异值哈希字符串
*/
public static String dHash(final BufferedImage bufferedImage) {
BufferedImage prepareImage;
if (bufferedImage.getWidth() != 9 || bufferedImage.getHeight() != 8) {
prepareImage = ImageUtils.processImage(bufferedImage, 9, 8, null);
} else {
prepareImage = bufferedImage;
}
double[][] grayMatrix = ImageUtils.grayMatrix(prepareImage);
StringBuilder dHash = new StringBuilder();
for (int x = 0 ; x < 8 ; x++) {
for (int y = 0 ; y < 8 ; y++) {
dHash.append((grayMatrix[x][y] > grayMatrix[x][y + 1]) ? "1" : "0");
}
}
return dHash.toString();
}
/**
* Calculate pHash of given image file
* 计算给定图片的感知哈希
*
* @param filePath Image file path
* 图片文件地址
*
* @return pHash string
* 感知哈希字符串
*/
public static String pHash(final String filePath) {
try {
return ImageUtils.pHash(FileUtils.getFile(filePath));
} catch (FileNotFoundException e) {
LOGGER.error("Not_Found_File_Error", filePath);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Stack_Message_Error", e);
}
return Globals.DEFAULT_VALUE_STRING;
}
}
/**
* Calculate pHash of given image file
* 计算给定图片的感知哈希
*
* @param file Image file instance
* 图片文件实例对象
*
* @return pHash string
* 感知哈希字符串
*/
public static String pHash(final File file) {
try {
return ImageUtils.pHash(ImageIO.read(file));
} catch (IOException e) {
LOGGER.error("Read_Files_Error");
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Stack_Message_Error", e);
}
return Globals.DEFAULT_VALUE_STRING;
}
}
/**
* Calculate pHash of given image file
* 计算给定图片的感知哈希
*
* @param bufferedImage Buffered image
* 缓冲图片实例对象
*
* @return pHash string
* 感知哈希字符串
*/
public static String pHash(final BufferedImage bufferedImage) {
BufferedImage prepareImage;
if (bufferedImage.getWidth() != 8 || bufferedImage.getHeight() != 8) {
prepareImage = ImageUtils.processImage(bufferedImage, 8, 8, null);
} else {
prepareImage = bufferedImage;
}
double[][] DCT = ImageUtils.applyDCT(prepareImage);
double total = 0.0;
for (int x = 0 ; x < 8 ; x++) {
for (int y = 0 ; y < 8 ; y++) {
total += DCT[x][y];
}
}
total -= DCT[0][0];
double average = total / 63;
StringBuilder pHash = new StringBuilder();
for (int x = 0 ; x < 8 ; x++) {
for (int y = 0 ; y < 8 ; y++) {
pHash.append((DCT[x][y] > average) ? "1" : "0");
}
}
return pHash.toString();
}
/**
* Add text/image watermark to the target image
* 添加文字/图片水印到目标图片
*
* @param graphics target image Graphics2D object
* 目标图片的Graphics2D实例对象
* @param width image width
* 图片宽度
* @param height image height
* 图片高度
* @param markOptions Mark options
* 水印选项
*/
private static void markImage(final Graphics2D graphics, final int width, final int height,
final MarkOptions markOptions) {
Optional.ofNullable(markOptions.retrievePosition(width, height))
.ifPresent(markPosition -> {
switch (markOptions.getMarkType()) {
case ICON:
try {
BufferedImage iconImg = ImageIO.read(FileUtils.getFile(markOptions.getMarkPath()));
if (iconImg != null && markOptions.getTransparency() >= 0
&& markOptions.getTransparency() <= 1) {
graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP,
markOptions.getTransparency()));
graphics.drawImage(iconImg, markPosition.getPositionX(), markPosition.getPositionY(), null);
graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
}
} catch (Exception e) {
LOGGER.error("Water_Mark_Image_Error");
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Stack_Message_Error", e);
}
}
break;
case TEXT:
if (markOptions.getMarkText() != null && markOptions.getFontName() != null
&& markOptions.getFontSize() > 0) {
graphics.setColor(markOptions.getColor());
graphics.setFont(new Font(markOptions.getFontName(), Font.PLAIN, markOptions.getFontSize()));
graphics.drawString(markOptions.getMarkText(), markPosition.getPositionX(), markPosition.getPositionY());
}
break;
}
});
}
/**
* Process image by given parameters
* 根据给定的参数处理图片
*
* @param srcImage Buffered image
* 缓冲图片实例对象
* @param targetWidth target width
* 图片调整后的宽度
* @param targetHeight target height
* 图片调整后的高度
* @param markOptions Mark options
* 水印选项
*
* @return true
success false
failed
*/
private static BufferedImage processImage(final BufferedImage srcImage, final int targetWidth,
final int targetHeight, final MarkOptions markOptions) {
if (srcImage != null && targetWidth > 0 && targetHeight > 0) {
BufferedImage bufferedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = bufferedImage.createGraphics();
graphics.drawImage(srcImage, 0, 0, targetWidth, targetHeight, null);
Optional.ofNullable(markOptions)
.ifPresent(options -> markImage(graphics, targetWidth, targetHeight, options));
graphics.dispose();
return bufferedImage;
}
return srcImage;
}
/**
* Convert bufferedImage to gray matrix
* 转换缓冲图片实例对象为灰度二维数组
*
* @param bufferedImage Buffered image
* 缓冲图片实例对象
*
* @return gray matrix
* 灰度二维数组
*/
private static double[][] grayMatrix(final BufferedImage bufferedImage) {
if (bufferedImage == null) {
return new double[0][0];
}
int width = bufferedImage.getWidth();
int height = bufferedImage.getHeight();
double[][] grayMatrix = new double[height][width];
for (int y = 0 ; y < height ; y++) {
for (int x = 0 ; x < width ; x++) {
int pixel = bufferedImage.getRGB(x, y);
grayMatrix[y][x] = ((pixel & 0xFF0000) >> 16) * 0.3
+ ((pixel & 0xFF00) >> 8) * 0.59
+ ((pixel & 0xFF) * 0.11);
}
}
return grayMatrix;
}
/**
* Process Discrete Cosine Transform to bufferedImage
* 对给定的缓冲图片实例对象做离散余弦变换
*
* @param bufferedImage Buffered image
* 缓冲图片实例对象
*
* @return Process result
* 处理结果
*/
private static double[][] applyDCT(final BufferedImage bufferedImage) {
if (bufferedImage == null) {
return new double[0][0];
}
int width = bufferedImage.getWidth();
int height = bufferedImage.getHeight();
int length = Math.max(width, height);
double[] uv = new double[length];
for (int i = 1 ; i < length ; i++) {
uv[i] = 1;
}
uv[0] = 1 / Math.sqrt(2.0);
double[][] DCT = new double[width][height];
for (int x = 0 ; x < width ; x++) {
for (int y = 0 ; y < height ; y++) {
double sum = 0.0;
int pixel = bufferedImage.getRGB(x, y);
double gray = ((pixel & 0xFF0000) >> 16) * 0.3
+ ((pixel & 0xFF00) >> 8) * 0.59
+ ((pixel & 0xFF) * 0.11);
for (int i = 0 ; i < width ; i++) {
for (int j = 0 ; j < height ; j++) {
sum += Math.cos(((2 * i + 1) / (width * height * 1.0)) * x * Math.PI)
* Math.cos(((2 * j + 1) / (width * height * 1.0)) * y * Math.PI)
* gray;
}
}
DCT[x][y] = sum * ((uv[x] * uv[y]) / 4.0);
}
}
return DCT;
}
}