文章目录

  • 图片读写
  • 小总结
  • 像素操作
  • 属性操作
  • 缩放
  • 格式转换
  • 其他
  • 统一缩放每个像素的值
  • 执行仿射变换
  • 执行卷积
  • 获取支持读取的文件格式
  • 获取支持写入的文件格式
  • 参考


图片读写

import javax.imageio.ImageIO;

class Test {
	public static void main(String[] args) 
		throws IOException 
	{
		// 从文件中读取
		BufferedImage image = ImageIO.read(new File("1.jpg"));
    	// 从链接中读取
    	BufferedImage image1 = ImageIO.read(new URL("http://www.example.com/image.jpg"));
    	// 从InputStream中读取,RPC中通过字节流传输图片的时候可能会用到
    	BufferedImage image2 = ImageIO.read(<inputstream>);
    }
}

import javax.imageio.ImageIO;

class Test {
	public static void main(String[] args) 
		throws IOException 
	{
		BufferedImage image = ImageIO.read(new File("1.jpg"));
		// 将图片内容输出到Stream
		ImageIO.write(image, "jpg", <outoutStream>);
		// 将图片写入文件
		ImageIO.write(image, "jpg", new File("out.jpg"));
    }
}

小总结

使用import javax.imageio.ImageIO;库,这里面封装了很多对图像读写的基础函数。类结构如下:

java获取图片的格式 java读取图片_缩放

像素操作

主要涉及到两个函数

  • BufferedImage.getRGB()
  • BufferedImage.setRGB()

找一个经典的通过Mask来融合背景的例子来说明,备注:没有使用png图片,Alpha通道没有数据

可以直接运行

import lombok.Builder;
import lombok.extern.slf4j.Slf4j;

import java.awt.image.BufferedImage;

/**
 * @author pengjian05
 */
@Slf4j
@Builder
public class CharacterMixer {
    /**
     * 前景图
     */
    private BufferedImage frontImage;

    /**
     * mask:表示前景图的透明度,纯白为全透明
     */
    private BufferedImage mask;

    /**
     * 背景图
     */
    private BufferedImage backImage;

    BufferedImage mix() throws UnsupportedOperationException {
        if (!ImageSize.equalsMulti(frontImage, mask, backImage)) {
            throw new UnsupportedOperationException(
                    String.format("images size should equal! front/mask/back = %s/%s/%s",
                            new ImageSize(frontImage),
                            new ImageSize(mask),
                            new ImageSize(backImage)));
        }

        return mixBackground();
    }

    // region private methods

    /**
     * @param x
     * @param y
     * @return
     */
    private int calInEachPixel(int x, int y) {
        var maskPixel = mask.getRGB(x, y);
        byte maskAlphaChannel = (byte) (maskPixel);
        int frontPixel = frontImage.getRGB(x, y);
        int backPixel = backImage.getRGB(x, y);

        if (Byte.toUnsignedInt(maskAlphaChannel) == (Byte.MAX_VALUE - Byte.MIN_VALUE)) {
            return frontPixel;
        } else if (Byte.toUnsignedInt(maskAlphaChannel) == 0) {
            return backPixel;
        }

        byte[] frontBytes = toByteArray(frontPixel);
        byte[] backBytes = toByteArray(backPixel);
        byte[] newByte = new byte[4];
        newByte[0] = (byte) Byte.toUnsignedInt((byte) (Byte.MAX_VALUE - Byte.MIN_VALUE));
        newByte[1] = calInEachByte(frontBytes[1], backBytes[1], maskAlphaChannel);
        newByte[2] = calInEachByte(frontBytes[2], backBytes[2], maskAlphaChannel);
        newByte[3] = calInEachByte(frontBytes[3], backBytes[3], maskAlphaChannel);
        return fromByteArray(newByte);
    }

    /**
     * 将背景图格式化成没有透明度的rgb图片
     * <p>
     * 1. 提取mask中的灰度作为透明度
     * 2. 针对前景图和背景图每一个通道都计算一下权重(mask本质就是前景透明度所占比重)
     * 3. 输出
     */
    private BufferedImage mixBackground() {
        var begin = System.currentTimeMillis();

        BufferedImage out = new BufferedImage(
                frontImage.getWidth(),
                frontImage.getHeight(),
                BufferedImage.TYPE_3BYTE_BGR);
        for (int i = 0; i < out.getWidth(); i++) {
            for (int j = 0; j < out.getHeight(); j++) {
                out.setRGB(i, j, calInEachPixel(i, j));
            }
        }
        log.info("mix background cost {}ms", System.currentTimeMillis() - begin);
        return out;
    }

    /**
     * @param front
     * @param back
     * @param weight
     * @return
     */
    private byte calInEachByte(byte front, byte back, byte weight) {
        float frontWeight = (float) ((float) (Byte.toUnsignedInt(weight)) / 255.0);
        float backWeight = 1 - frontWeight;
        return (byte) (
                (Byte.toUnsignedInt(front)) * frontWeight
                        + (Byte.toUnsignedInt(back)) * backWeight);
    }
	
	/**
     * @param bytes
     * @return
     * @throws RuntimeException
     */
    public static int fromByteArray(byte[] bytes) throws RuntimeException {
        if (bytes.length < 4) {
            throw new RuntimeException(String.format("array too small: %s < %s", bytes.length, 4));
        }
        return fromBytes(bytes[0], bytes[1], bytes[2], bytes[3]);
    }

    /**
     * @param b1
     * @param b2
     * @param b3
     * @param b4
     * @return
     */
    public static int fromBytes(byte b1, byte b2, byte b3, byte b4) {
        return b1 << 24 | (b2 & 255) << 16 | (b3 & 255) << 8 | b4 & 255;
    }

    /**
     * @param value
     * @return
     */
    public static byte[] toByteArray(int value) {
        return new byte[]{(byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) value};
    }
    // endregion
}

属性操作

import javax.imageio.ImageIO;

class Test {
	public static void main(String[] args) 
		throws IOException 
	{
		// 从文件中读取
		BufferedImage image = ImageIO.read(new File("1.jpg"));
    	
    	System.out.println(image.getHeight());
        
        System.out.println(image.getWidth());
        
        System.out.println(image.getType());   // 内部规定的颜色类型
        
        System.out.println(image.getTransparency());   // 获取透明类型,分为(不透明,bit透明(某个像素是透明或不透明两种),透明)
        
        System.out.println(image.getColorModel());
    }
}

缩放

private BufferedImage resizeImage(BufferedImage image, ImageSize target) {
    BufferedImage resizedImage = new BufferedImage(target.getWidth(), target.getHeight(), image.getType());
    
    Image originalImage = image.getScaledInstance(target.getWidth(), target.getHeight(), Image.SCALE_DEFAULT);
    
    var g = resizedImage.createGraphics();
    g.drawImage(originalImage, 0, 0, target.getWidth(), target.getHeight(), null);
    g.dispose();
    
    return resizedImage;
}

在Image对象中有多个缩放算法可以选

  • SCALE_DEFAULT
  • SCALE_FAST
  • SCALE_SMOOTH
  • SCALE_REPLICATE
  • SCALE_AREA_AVERAGING

格式转换

转换成Gray图片

private static BufferedImage convertToGray(BufferedImage image) {
    if (image.getType() == BufferedImage.TYPE_BYTE_GRAY) {
        return image;
    }

    ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
    ColorConvertOp op = new ColorConvertOp(cs, null);
    return op.filter(image, null);
}

其他图片格式转换也可以通过ColorConvertOp支持的颜色空间(ColorSpace)来完成颜色转换,Java Image支持的颜色空间有:

  • CS_sRGB
  • CS_LINEAR_RGB
  • CS_CIEXYZ
  • CS_PYCC
  • CS_GRAY

注:CS=ColorSpace
更多颜色转化细节可以参考ColorConvertOp的接口

其他

统一缩放每个像素的值

RescaleOp通过将每个像素的样本值乘以一个缩放因子,然后加上一个偏移量,此类对源图像中数据进行逐像素重缩放。缩放后的样本值被限制在目标图像中的最小/最大可表示形式。

重缩放操作的伪代码如下:

for each pixel from Source object {
    for each band/component of the pixel {
        dstElement = (srcElement*scaleFactor) + offset
    }
}

执行仿射变换

执行卷积

获取支持读取的文件格式

ImageIO.getReaderFileSuffixes();

获取支持写入的文件格式

ImageIO.getWriterFileSuffixes();