文章目录
- 图片读写
- 读
- 写
- 小总结
- 像素操作
- 属性操作
- 缩放
- 格式转换
- 其他
- 统一缩放每个像素的值
- 执行仿射变换
- 执行卷积
- 获取支持读取的文件格式
- 获取支持写入的文件格式
- 参考
图片读写
读
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
}
}
执行仿射变换
执行卷积
获取支持读取的文件格式
ImageIO.getReaderFileSuffixes();
获取支持写入的文件格式
ImageIO.getWriterFileSuffixes();