Java 圖片 基礎操作

圖片讀寫

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;庫,這裏面封裝了很多對圖像讀寫的基礎函數。類結構如下:
在這裏插入圖片描述

像素操作

主要涉及到兩個函數

  • 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
    }
}

關於RescaleOp的詳細資料參考:HERE

執行仿射變換

參考:AffineTransformOp

執行卷積

參考:ConvolveOp

獲取支持讀取的文件格式

ImageIO.getReaderFileSuffixes();

獲取支持寫入的文件格式

ImageIO.getWriterFileSuffixes();

參考

  1. RasterOp
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章