圖片讀寫
讀
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
執行仿射變換
執行卷積
參考:ConvolveOp
獲取支持讀取的文件格式
ImageIO.getReaderFileSuffixes();
獲取支持寫入的文件格式
ImageIO.getWriterFileSuffixes();