net.gdface.image.ImageUtil Maven / Gradle / Ivy
package net.gdface.image;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Iterator;
import javax.imageio.IIOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import net.gdface.utils.Assert;
/**
* 图像工具类
* @author guyadong
*
*/
public class ImageUtil {
/**
* 对图像进行缩放
* @param source 原图
* @param targetWidth 缩放后图像宽度
* @param targetHeight 缩放后图像高度
* @param constrain 为true时等比例缩放,targetWidth,targetHeight为缩放图像的限制尺寸
* @return
*/
public static BufferedImage resize(BufferedImage source, int targetWidth, int targetHeight,boolean constrain) {
if(constrain){
double aspectRatio = (double)source.getWidth()/source.getHeight();
double sx = (double) targetWidth / source.getWidth();
double sy = (double) targetHeight / source.getHeight();
if (sx > sy) {
targetWidth = (int) Math.round(targetHeight*aspectRatio);
} else {
targetHeight = (int) Math.round(targetWidth/aspectRatio);
}
}
int type = source.getType();
BufferedImage target = null;
if (type == BufferedImage.TYPE_CUSTOM) {
ColorModel cm = source.getColorModel();
WritableRaster raster = cm.createCompatibleWritableRaster(targetWidth, targetHeight);
target = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
} else {
target = new BufferedImage(targetWidth, targetHeight, type);
}
Graphics2D g = target.createGraphics();
try{
g.drawImage(
source.getScaledInstance(targetWidth, targetHeight, Image.SCALE_SMOOTH),
0, 0, null);
}finally{
g.dispose();
}
return target;
}
public static byte[] wirteJPEGBytes(BufferedImage source){
return wirteJPEGBytes(source,null);
}
public static byte[] wirteBMPBytes(BufferedImage source){
return wirteBytes(source,"BMP");
}
public static byte[] wirtePNGBytes(BufferedImage source){
return wirteBytes(source,"PNG");
}
public static byte[] wirteGIFBytes(BufferedImage source){
return wirteBytes(source,"GIF");
}
/**
* 将原图压缩生成jpeg格式的数据
* @param source
* @return
* @see #wirteBytes(BufferedImage, String)
*/
public static byte[] wirteJPEGBytes(BufferedImage source,Float compressionQuality){
return wirteBytes(source,"JPEG",compressionQuality);
}
public static byte[] wirteBytes(BufferedImage source,String formatName){
return wirteBytes(source,formatName,null);
}
/**
* 将{@link BufferedImage}生成formatName指定格式的图像数据
* @param source
* @param formatName 图像格式名,图像格式名错误则抛出异常,可用的值 'BMP','PNG','GIF','JPEG'
* @param compressionQuality 压缩质量(0.0~1.0),超过此范围抛出异常,为null使用默认值
* @return
*/
public static byte[] wirteBytes(BufferedImage source,String formatName,Float compressionQuality){
ByteArrayOutputStream output = new ByteArrayOutputStream();
try {
wirte(source, formatName, compressionQuality, output);
} catch (IOException e) {
throw new RuntimeException(e);
}
return output.toByteArray();
}
/**
* 将{@link BufferedImage}生成formatName指定格式的图像数据
* @param source
* @param formatName 图像格式名,图像格式名错误则抛出异常,可用的值 'BMP','PNG','GIF','JPEG'
* @param compressionQuality 压缩质量(0.0~1.0),超过此范围抛出异常,为null使用默认值
* @param output 输出流
* @throws IOException
*/
public static void wirte(BufferedImage source,String formatName,Float compressionQuality,OutputStream output) throws IOException{
Assert.notNull(source, "source");
Assert.notEmpty(formatName, "formatName");
Assert.notNull(output, "output");
Graphics2D g = null;
try {
// 对于某些格式的图像(如png),直接调用ImageIO.write生成jpeg可能会失败
// 所以先尝试直接调用ImageIO.write,如果失败则用Graphics生成新的BufferedImage再调用ImageIO.write
for(BufferedImage s=source;!write(s, formatName, output,compressionQuality);){
if(null!=g){
throw new IllegalArgumentException(String.format("not found writer for '%s'",formatName));
}
s = new BufferedImage(source.getWidth(),
source.getHeight(), BufferedImage.TYPE_INT_RGB);
g = s.createGraphics();
g.drawImage(source, 0, 0,null);
}
} finally {
if (null != g){
g.dispose();
}
}
}
/**
* 对原图创建缩略图对象
* 如果原图尺寸小于指定的缩略图尺寸则直接返回原图对象的副本
* @param source 原图对象
* @param thumbnailWidth 缩略图宽度
* @param thumbnailHeight 缩略图高度
* @param ratioThreshold 最大宽高比阀值(宽高中较大的值/较小的值),<此值时对原图等比例缩放,>=此值时从原图切出中间部分图像再等比例缩放
* @return
*/
public static BufferedImage createThumbnail(BufferedImage source,int thumbnailWidth,int thumbnailHeight,double ratioThreshold) {
int w = source.getWidth();
int h = source.getHeight();
if (w < thumbnailWidth && h < thumbnailHeight) {
// 返回原图的副本
return source.getSubimage(0, 0, w, h);
}
double thumAspectRatio = (double) thumbnailWidth / thumbnailHeight;
double wh_sca = w > h ? (double) w / h : (double) h / w;
if (wh_sca >= ratioThreshold) {
if (w > h) {
int fw = (int) (thumAspectRatio * h);
if (h <= thumbnailHeight) {
return source.getSubimage((w - fw) / 2, 0, fw, h);
} else {
return resize(source.getSubimage( (w - fw) / 2, 0, fw, h), thumbnailWidth,
thumbnailHeight,true);
}
} else {
int fh = (int) (thumAspectRatio * w);
if (w <= thumbnailWidth) {
return source.getSubimage( 0, (h - fh) / 2, w, fh);
} else {
return resize(source.getSubimage( 0, (h - fh) / 2, w, fh), thumbnailWidth,
thumbnailHeight,true);
}
}
} else {
return resize(source, thumbnailWidth, thumbnailHeight,true);
}
}
/**
* 对原图创建JPEG格式的缩略图
* @param imageBytes 图像数据字节数组
* @param thumbnailWidth
* @param thumbnailHeight
* @param ratioThreshold
* @return 返回jpeg格式的图像数据字节数组
* @see #createThumbnail(BufferedImage, int, int, double)
* @see #wirteJPEGBytes(BufferedImage)
*/
public static byte[] createJPEGThumbnail(byte[] imageBytes,int thumbnailWidth,int thumbnailHeight,double ratioThreshold) {
Assert.notEmpty(imageBytes, "imageBytes");
try {
BufferedImage source = ImageIO.read(new ByteArrayInputStream(imageBytes));
if(null==source){
throw new IllegalArgumentException("unsupported image format");
}
BufferedImage thumbnail = createThumbnail(source, thumbnailWidth, thumbnailHeight, ratioThreshold);
return wirteJPEGBytes(thumbnail);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* @param image
* @param bandOffset 用于判断通道顺序
* @return
*/
private static boolean equalBandOffsetWith3Byte(BufferedImage image,int[] bandOffset){
if(image.getType()==BufferedImage.TYPE_3BYTE_BGR){
if(image.getData().getSampleModel() instanceof ComponentSampleModel){
ComponentSampleModel sampleModel = (ComponentSampleModel)image.getData().getSampleModel();
if(Arrays.equals(sampleModel.getBandOffsets(), bandOffset)){
return true;
}
}
}
return false;
}
public static boolean isBGRA(BufferedImage image){
return image.getType()==BufferedImage.TYPE_4BYTE_ABGR
|| image.getType()==BufferedImage.TYPE_4BYTE_ABGR_PRE;
}
public static boolean isGray(BufferedImage image){
return image.getType()==BufferedImage.TYPE_BYTE_GRAY;
}
public static boolean isBGR3Byte(BufferedImage image){
return equalBandOffsetWith3Byte(image,new int[]{0, 1, 2});
}
public static boolean isRGB3Byte(BufferedImage image){
return equalBandOffsetWith3Byte(image,new int[]{2, 1, 0});
}
/**
* 对图像解码返回RGB格式矩阵数据
* @param image
* @return
*/
public static byte[] getMatrixRGB(BufferedImage image) {
if(null==image){
throw new NullPointerException();
}
byte[] matrixRGB;
if(isRGB3Byte(image)){
matrixRGB= (byte[]) image.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
}else{
// 转RGB格式
BufferedImage rgbImage = new BufferedImage(image.getWidth(), image.getHeight(),
BufferedImage.TYPE_3BYTE_BGR);
new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null).filter(image, rgbImage);
matrixRGB= (byte[]) rgbImage.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
}
return matrixRGB;
}
/**
* 对图像解码返回RGB格式矩阵数据
* @param image
* @return
*/
public static byte[] getMatrixRGBA(BufferedImage image) {
if(null==image){
throw new NullPointerException();
}
byte[] matrixRGBA;
if(isBGRA(image)){
matrixRGBA= (byte[]) image.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
}else{
// 转RGBA格式
BufferedImage rgbaImage = new BufferedImage(image.getWidth(), image.getHeight(),
BufferedImage.TYPE_4BYTE_ABGR);
new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null).filter(image, rgbaImage);
matrixRGBA= (byte[]) rgbaImage.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
}
return matrixRGBA;
}
/**
* 对图像解码返回BGR格式矩阵数据
* @param image
* @return
*/
public static byte[] getMatrixBGR(BufferedImage image){
if(null==image){
throw new NullPointerException();
}
byte[] matrixBGR;
if(isBGR3Byte(image)){
matrixBGR= (byte[]) image.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
}else{
// ARGB格式图像数据
int intrgb[]=image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth());
matrixBGR=new byte[image.getWidth() * image.getHeight()*3];
// ARGB转BGR格式
for(int i=0,j=0;i>8)&0xff);
matrixBGR[j+2]=(byte) ((intrgb[i]>>16)&0xff);
}
}
return matrixBGR;
}
/**
* 对图像解码返回BGR格式矩阵数据
* @param image
* @return
*/
public static byte[] getMatrixGRAY(BufferedImage image){
if(null==image){
throw new NullPointerException();
}
byte[] matrixGray;
if(isGray(image)){
matrixGray= (byte[]) image.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
}else{
// 图像转灰
BufferedImage gray = new BufferedImage(image.getWidth(), image.getHeight(),
BufferedImage.TYPE_BYTE_GRAY);
new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null).filter(image, gray);
matrixGray= (byte[]) gray.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
}
return matrixGray;
}
/**
* 从源{@link BufferedImage}对象创建一份拷贝
* @param src
* @param imageType 创建的{@link BufferedImage}目标对象类型
* @return 返回拷贝的对象
* @see BufferedImage#BufferedImage(int, int, int)
*/
public static BufferedImage copy(Image src,int imageType){
if(null==src){
throw new NullPointerException("src must not be null");
}
BufferedImage dst = new BufferedImage(src.getWidth(null), src.getHeight(null), imageType);
Graphics g = dst.getGraphics();
try{
g.drawImage(src, 0, 0, null);
return dst;
}finally{
g.dispose();
}
}
/**
* 创建{@link BufferedImage#TYPE_3BYTE_BGR}类型的拷贝
* @param src
* @return 返回拷贝的对象
* @see #copy(Image, int)
*/
public static BufferedImage copy(BufferedImage src){
return copy(src,BufferedImage.TYPE_3BYTE_BGR);
}
/**
* 对原图缩放,返回缩放后的新对象
* @param src
* @param scale
* @return 返回缩放后的新对象
*/
public static BufferedImage scale(BufferedImage src,double scale){
if(null==src){
throw new NullPointerException("src must not be null");
}
if(0>=scale){
throw new IllegalArgumentException("scale must >0");
}
int width = src.getWidth(); // 源图宽
int height = src.getHeight(); // 源图高
Image image = src.getScaledInstance((int)Math.round(width * scale), (int)Math.round(height * scale), Image.SCALE_SMOOTH);
return copy(image,BufferedImage.TYPE_3BYTE_BGR);
}
/**
* 将{@link Image}图像上下左右扩充指定的尺寸
* @param src
* @param imageType
* @param left
* @param top
* @param right
* @param bottom
* @return 返回扩展尺寸后的新对象
*/
public static BufferedImage growCanvas(Image src,int imageType,int left,int top,int right,int bottom){
if(null==src){
throw new NullPointerException("src must not be null");
}
if(left<0||top<0||right<0||bottom<0){
throw new IllegalArgumentException("left,top,right,bottom must >=0");
}
BufferedImage dst = new BufferedImage(src.getWidth(null)+left+right, src.getHeight(null)+top+bottom, imageType);
Graphics g = dst.getGraphics();
try{
g.setColor(Color.BLACK);
g.fillRect(0,0, dst.getWidth(), dst.getHeight());
g.drawImage(src, left, top, null);
return dst;
}finally{
g.dispose();
}
}
/**
* @param src
* @param left
* @param top
* @param right
* @param bottom
* @return
* @see #growCanvas(Image, int, int, int, int, int)
*/
public static BufferedImage growCanvas(Image src,int left,int top,int right,int bottom){
return growCanvas(src,BufferedImage.TYPE_3BYTE_BGR,left,top,right,bottom);
}
/**
* 将{@link Image}图像(向右下)扩充为正文形(尺寸长宽最大边)
* @param src
* @return
*/
public static BufferedImage growSquareCanvas(Image src){
if(null==src){
throw new NullPointerException("src must not be null");
}
int width=src.getWidth(null);
int height=src.getHeight(null);
int size=Math.max(width, height);
return growCanvas(src,BufferedImage.TYPE_3BYTE_BGR,0,0,size-width,size-height);
}
/**
* Returns ImageWriter
instance according to given
* rendered image and image format or null
if there
* is no appropriate writer.
*/
private static ImageWriter getImageWriter(RenderedImage im,
String formatName) {
ImageTypeSpecifier type =
ImageTypeSpecifier.createFromRenderedImage(im);
Iterator iter = ImageIO.getImageWriters(type, formatName);
if (iter.hasNext()) {
return iter.next();
} else {
return null;
}
}
/**
* 将原图压缩生成{@code formatName}指定格式的数据
* 除了可以指定生成的图像质量之外,
* 其他行为与{@link ImageIO#write(RenderedImage, String, OutputStream)}相同
* @param source
* @param formatName
* @param output
* @param compressionQuality 指定图像质量,为{@code null}调用{@link ImageIO#write(RenderedImage, String, OutputStream)}
* @return
* @throws IOException
*/
public static boolean write(RenderedImage source,
String formatName,
OutputStream output,
Float compressionQuality) throws IOException{
if(null == compressionQuality){
return ImageIO.write(source, formatName, output);
}
ImageWriter writer = getImageWriter(source,formatName);
if(null == writer){
return false;
}
ImageOutputStream stream = null;
try {
stream = ImageIO.createImageOutputStream(output);
} catch (IOException e) {
throw new IIOException("Can't create output stream!", e);
}
writer.setOutput(stream);
ImageWriteParam param = writer.getDefaultWriteParam();
try{
if(param.canWriteCompressed()){
try{
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(compressionQuality);
}catch(RuntimeException e){
}
}
writer.write(null, new IIOImage(source, null, null), param);
return true;
} finally {
writer.dispose();
stream.flush();
}
}
/**
* 从RGB格式图像矩阵数据创建一个BufferedImage
* @param matrixRGB RGB格式图像矩阵数据,为null则创建一个指定尺寸的空图像
* @param width
* @param height
* @return
*/
public static BufferedImage createRGBImage(byte[] matrixRGB,int width,int height){
int bytePerPixel = 3;
Assert.isTrue(null==matrixRGB || matrixRGB.length==width*height*bytePerPixel,"invalid image argument");
DataBufferByte dataBuffer = null==matrixRGB ? null : new DataBufferByte(matrixRGB, matrixRGB.length);
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
int[] bOffs = {0, 1, 2};
ComponentColorModel colorModel = new ComponentColorModel(cs, false, false,
Transparency.OPAQUE,
DataBuffer.TYPE_BYTE);
WritableRaster raster = null != dataBuffer
? Raster.createInterleavedRaster(dataBuffer, width, height, width*bytePerPixel, bytePerPixel, bOffs, null)
: Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height,width*bytePerPixel, bytePerPixel, bOffs, null);
BufferedImage img = new BufferedImage(colorModel,raster,colorModel.isAlphaPremultiplied(),null);
return img;
}
/**
* 从RGBA格式图像矩阵数据创建一个BufferedImage
* 该方法删除了alpha通道
* @param matrixRGBA RGBA格式图像矩阵数据,为null则创建一个指定尺寸的空图像
* @param width
* @param height
* @return
*/
public static BufferedImage createRGBAImage(byte[] matrixRGBA,int width,int height){
int bytePerPixel = 4;
Assert.isTrue(null==matrixRGBA || matrixRGBA.length==width*height*bytePerPixel,"invalid image argument");
DataBufferByte dataBuffer = null==matrixRGBA ? null : new DataBufferByte(matrixRGBA, matrixRGBA.length);
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
int[] bOffs = {0, 1, 2};
ComponentColorModel colorModel = new ComponentColorModel(cs, false, false,
Transparency.OPAQUE,
DataBuffer.TYPE_BYTE);
WritableRaster raster = null != dataBuffer
? Raster.createInterleavedRaster(dataBuffer, width, height, width*bytePerPixel, bytePerPixel, bOffs, null)
: Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height,width*bytePerPixel, bytePerPixel, bOffs, null);
BufferedImage img = new BufferedImage(colorModel,raster,colorModel.isAlphaPremultiplied(),null);
return img;
}
private static void assertContains(final Rectangle parent, String argParent, final Rectangle sub, final String argSub)
throws IllegalArgumentException {
if(!parent.contains(sub))
throw new IllegalArgumentException(String.format(
"the %s(X%d,Y%d,W%d,H%d) not contained by %s(X%d,Y%d,W%d,H%d)",
argSub,sub.x, sub.y,sub.width, sub.height, argParent,parent.x,parent.y,parent.width, parent.height));
}
/**
* 从matrix矩阵中截取rect指定区域的子矩阵
* @param matrix 3byte(RGB/BGR) 图像矩阵
* @param matrixRect 矩阵尺寸
* @param rect 截取区域
* @return
*/
public static byte[] cutMatrix(byte[] matrix,Rectangle matrixRect,Rectangle rect) {
// 解码区域,为null或与图像尺寸相等时直接返回 matrix
if((rect == null || rect.equals(matrixRect)))
return matrix;
else{
// 如果指定的区域超出图像尺寸,则抛出异常
ImageUtil.assertContains(matrixRect, "srcRect", rect ,"rect");
byte[] dstArray=new byte[rect.width*rect.height*3];
// 从 matrix 中复制指定区域的图像数据返回
for(int dstIndex=0,srcIndex=(rect.y*matrixRect.width+rect.x)*3,y=0;
y
© 2015 - 2025 Weber Informatics LLC | Privacy Policy