在图像上查找透明像素并使相同像素在另一个图像上透明
Finding transparent pixels on an image and making same pixels transparent on another image
我有两张图片。第二个是对第一个蒙版应用某种掩码的结果。我需要的是获得该蒙版并能够将其应用于其他图像。
这是两张图片:normal, tattered.
如你所见,第二个边缘破烂不堪,是透明的,不是白色的。
(还有一些模糊,如果有办法我可以找出它到底是什么模糊,那就太好了,但这里真的没有必要)
我需要的是能够从第一个图像创建第二个图像。
理论上,我应该创建一个遮罩 - 一个大小相同、颜色任意且每个像素的透明度为 0 或 255 的图像,具体取决于上面第二张图像中相同像素的透明度值。然后我可以将任何输入图像的像素的 alpha 设置为来自此掩码的 alpha 值。
但是,我不知道如何实际操作。我在 java 中使用 BufferedImage 尝试过,但是它不起作用。当我尝试从所选像素的颜色中获取 Alpha 时,它始终为 255,即使对于应该透明的像素也是如此。我确实设法在 Processing 中获得了 alpha 值(它们实际上不仅仅是 0 或 255,中间有很多值),但是,当我尝试将此值应用于新图像并保存时,它保存为完全不透明的图像,当我加载它,alpha值都是255.
PImage mask = loadImage("some/path/1.png");
PImage img = loadImage("some/path/2.png");
img.loadPixels();
for (int x = 0; x < img.width; x++) {
for (int y = 0; y < img.height; y++) {
color maskColor = mask.get(x, y);
if (alpha(maskColor) < 255) {
color imgColor = img.get(x, y);
img.pixels[y*img.width + x] = color(red(imgColor), green(imgColor), blue(imgColor), alpha(maskColor));
}
}
}
img.updatePixels();
img.save("some/path/3.png");
您可以尝试区分原始图像和破损图像的 alpha 通道。
PImage tattered = loadImage("some/path/1.png");
PImage img = loadImage("some/path/2.png");
PImage mask = image.copy();
img.loadPixels();
for (int x = 0; x < img.width; x++) {
for (int y = 0; y < img.height; y++) {
mask[x][y] = abs(alpha(img.get(x, y)) - alpha(tattered.get(x, y)));
}
}
mask.updatePixels();
mask.save("some/path/3.png");
您也可以将破烂图像用作其他图像的蒙版。您只需要掩码中的 alpha 信息。
使用 BufferedImage 创建破烂边框的实现:
public class Test {
public static void main(String[] args) throws IOException {
BufferedImage mask = loadImage("e:\mask.png");
BufferedImage img = loadImage("e:\1.png");
int width = mask.getWidth();
int height = mask.getHeight();
BufferedImage processed = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int rgb = mask.getRGB(x,y);
int maskAlpha = alpha(rgb);
int imgColor = img.getRGB(x, y);
if (maskAlpha < 255) {
processed.setRGB(x,y,maskAlpha(imgColor, maskAlpha));
} else {
processed.setRGB(x,y,imgColor);
}
}
}
writeImage(processed, "e:\2.png");
}
static BufferedImage loadImage(String imagePath) {
File file = new File(imagePath);
BufferedImage image = null;
try {
image = ImageIO.read(file);
} catch (IOException e) {
e.printStackTrace();
}
return image;
}
static void writeImage(BufferedImage img,String filePath){
String format = filePath.substring(filePath.indexOf('.')+1);
//Get Picture Format
System.out.println(format);
try {
ImageIO.write(img,format,new File(filePath));
} catch (IOException e) {
e.printStackTrace();
}
}
static int maskAlpha(int rgb, int alpha) {
//strip alpha from original color
int color = (0x00ffffff&rgb);
return color + ((alpha)<<24);
}
static int alpha(int rgb) {
return (0xff&(rgb>>24));
}
static int red(int rgb) {
return (0xff&rgb);
}
static int green(int rgb) {
return (0xff&(rgb>>8));
}
static int blue(int rgb) {
return (0xff&(rgb>>16));
}
}
这里BufferedImage.TYPE_4BYTE_ABGR表示
Represents an image with 8-bit RGBA color components with the colors
Blue, Green, and Red stored in 3 bytes and 1 byte of alpha. The image
has a ComponentColorModel with alpha. The color data in this image is
considered not to be premultiplied with alpha. The byte data is
interleaved in a single byte array in the order A, B, G, R from lower
to higher byte addresses within each pixel.
表示颜色整数是32位的,按abgr的顺序存放在java中,即前8位是alpha,后8位是r,所以可以得到argb值如下:
int r = (0xff&rgb);
int g = (0xff&(rgb>>8));
int b = (0xff&(rgb>>16));
int a = (0xff&(rgb>>24));
使用 BufferedImage
、Graphics2D
和 AlphaComposite
,您可以这样组合图像:
BufferedImage image = ImageIO.read(new File("image.png"));
BufferedImage mask = ImageIO.read(new File("mask.png"));
BufferedImage composed = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = composed.createGraphics();
try {
g.setComposite(AlphaComposite.Src); // Possibly faster than SrcOver
g.drawImage(image, 0, 0, null);
// Clear out the transparent parts from mask
g.setComposite(AlphaComposite.DstIn);
g.drawImage(mask, 0, 0, null);
}
finally {
g.dispose();
}
if (!ImageIO.write(composed, "PNG", new File("composed.png"))) {
throw new IIOException("Could not write image using PNG format: " + composed);
}
PS:如果您知道您的源图像(上面代码中的image
)包含透明度并且之后不需要原始图像,您可以直接在其上合成遮罩。这将更快并且使用更少的内存,因为您跳过了内存分配和额外的组合。
我不确定为什么在您的特定示例中需要进行检查。如果目标图像不使用 alpha 通道(它都是不透明的),您可以简单地使用源图像的 alpha 通道覆盖数据。
顺便说一句,如果您使用 pixels[]
,一个循环应该可以:
PImage withAlpha;
PImage noAlpha;
void setup(){
size(120, 130);
background(0);
withAlpha = loadImage("8fXFk.png");
noAlpha = loadImage("AOsi0.png");
copyAlphaChannel(withAlpha, noAlpha);
}
void draw(){
background(map(sin(frameCount * 0.1), -1.0, 1.0, 0, 192), 0, 0);
image(withAlpha, 0, 0);
image(noAlpha, 60, 0);
}
void copyAlphaChannel(PImage src, PImage dst){
// quick error check
if(src.width != dst.width || src.height != dst.height){
println(String.format("error, mismatching dimensions src(%d,%d) != dst(%d,%d)",
src.width, src.height, dst.width, dst.height));
return;
}
// load pixel data
src.loadPixels();
dst.loadPixels();
int numPixels = src.pixels.length;
// for each pixel
for(int i = 0 ; i < numPixels; i++){
// extract source alpha
int srcAlpha = (src.pixels[i] >> 24) & 0xFF;
// apply it to the destination image
// src alpha | dst RGB
dst.pixels[i] = srcAlpha << 24 | (dst.pixels[i] & 0xFFFFFF);
}
dst.updatePixels();
}
更新感谢您指出这一点:我错过了这个细节。
PImage
可以有三种格式:
RGB (=1)
ARGB (=2)
ALPHA(=4)
将 RGB
格式 PImage
转换为 ARGB
格式的一种解决方法是应用不透明的 mask()
:
PImage withAlpha;
PImage noAlpha;
void setup(){
size(120, 130);
background(0);
withAlpha = loadImage("8fXFk.png");
noAlpha = loadImage("AOsi0.png");
println("before",withAlpha.format, noAlpha.format, ARGB, RGB); // notice noAlpha's format is RGB
forceAlphaChannel(noAlpha);
println("after",withAlpha.format, noAlpha.format, ARGB, RGB); // notice noAlpha's format is ARGB
copyAlphaChannel(withAlpha, noAlpha);
noAlpha.save("test.png");
}
void draw(){
background(map(sin(frameCount * 0.1), -1.0, 1.0, 0, 192), 0, 0);
image(withAlpha, 0, 0);
image(noAlpha, 60, 0);
}
void forceAlphaChannel(PImage src){
// make an opaque mask
PImage mask = createImage(src.width, src.height, ALPHA);
java.util.Arrays.fill(mask.pixels, color(255));
mask.updatePixels();
// apply the mask force the RGB image into ARGB format
src.mask(mask);
}
void copyAlphaChannel(PImage src, PImage dst){
// quick error check
if(src.width != dst.width || src.height != dst.height){
println(String.format("error, mismatching dimensions src(%d,%d) != dst(%d,%d)",
src.width, src.height, dst.width, dst.height));
return;
}
// load pixel data
src.loadPixels();
dst.loadPixels();
int numPixels = src.pixels.length;
// for each pixel
for(int i = 0 ; i < numPixels; i++){
// extract source alpha
int srcAlpha = (src.pixels[i] >> 24) & 0xFF;
// apply it to the destination image
// src alpha | dst RGB
dst.pixels[i] = srcAlpha << 24 | (dst.pixels[i] & 0xFFFFFF);
}
dst.updatePixels();
}
由于以上循环多次像素(一次创建蒙版并再次应用它),首先创建一个 ARGB
PImage
可能更有效,然后从一个 PImage
复制 RGB
数据,从另一个 ALPHA
复制数据:
PImage withAlpha;
PImage noAlpha;
void setup(){
size(120, 130);
background(0);
withAlpha = loadImage("8fXFk.png");
noAlpha = loadImage("AOsi0.png");
println("before",withAlpha.format, noAlpha.format, ARGB, RGB); // notice noAlpha's format is RGB
noAlpha = getAlphaChannelCopy(withAlpha, noAlpha);
println("after",withAlpha.format, noAlpha.format, ARGB, RGB); // notice noAlpha's format is ARGB
noAlpha.save("test.png");
}
void draw(){
background(map(sin(frameCount * 0.1), -1.0, 1.0, 0, 192), 0, 0);
image(withAlpha, 0, 0);
image(noAlpha, 60, 0);
}
// copy src alpha and dst rgb into new ARGB PImage
PImage getAlphaChannelCopy(PImage src, PImage dst){
// quick error check
if(src.width != dst.width || src.height != dst.height){
println(String.format("error, mismatching dimensions src(%d,%d) != dst(%d,%d)",
src.width, src.height, dst.width, dst.height));
return null;
}
PImage out = createImage(src.width, src.height, ARGB);
// load pixel data
src.loadPixels();
dst.loadPixels();
out.loadPixels();
int numPixels = src.pixels.length;
// for each pixel
for(int i = 0 ; i < numPixels; i++){
// extract source alpha
int srcAlpha = (src.pixels[i] >> 24) & 0xFF;
// apply it to the destination image
// src alpha | dst RGB
out.pixels[i] = srcAlpha << 24 | (dst.pixels[i] & 0xFFFFFF);
}
out.updatePixels();
return out;
}
(这里唯一的小缺点是您 loadPixels()
三次:每张图片一次。)
我有两张图片。第二个是对第一个蒙版应用某种掩码的结果。我需要的是获得该蒙版并能够将其应用于其他图像。 这是两张图片:normal, tattered.
如你所见,第二个边缘破烂不堪,是透明的,不是白色的。 (还有一些模糊,如果有办法我可以找出它到底是什么模糊,那就太好了,但这里真的没有必要)
我需要的是能够从第一个图像创建第二个图像。
理论上,我应该创建一个遮罩 - 一个大小相同、颜色任意且每个像素的透明度为 0 或 255 的图像,具体取决于上面第二张图像中相同像素的透明度值。然后我可以将任何输入图像的像素的 alpha 设置为来自此掩码的 alpha 值。
但是,我不知道如何实际操作。我在 java 中使用 BufferedImage 尝试过,但是它不起作用。当我尝试从所选像素的颜色中获取 Alpha 时,它始终为 255,即使对于应该透明的像素也是如此。我确实设法在 Processing 中获得了 alpha 值(它们实际上不仅仅是 0 或 255,中间有很多值),但是,当我尝试将此值应用于新图像并保存时,它保存为完全不透明的图像,当我加载它,alpha值都是255.
PImage mask = loadImage("some/path/1.png");
PImage img = loadImage("some/path/2.png");
img.loadPixels();
for (int x = 0; x < img.width; x++) {
for (int y = 0; y < img.height; y++) {
color maskColor = mask.get(x, y);
if (alpha(maskColor) < 255) {
color imgColor = img.get(x, y);
img.pixels[y*img.width + x] = color(red(imgColor), green(imgColor), blue(imgColor), alpha(maskColor));
}
}
}
img.updatePixels();
img.save("some/path/3.png");
您可以尝试区分原始图像和破损图像的 alpha 通道。
PImage tattered = loadImage("some/path/1.png");
PImage img = loadImage("some/path/2.png");
PImage mask = image.copy();
img.loadPixels();
for (int x = 0; x < img.width; x++) {
for (int y = 0; y < img.height; y++) {
mask[x][y] = abs(alpha(img.get(x, y)) - alpha(tattered.get(x, y)));
}
}
mask.updatePixels();
mask.save("some/path/3.png");
您也可以将破烂图像用作其他图像的蒙版。您只需要掩码中的 alpha 信息。
使用 BufferedImage 创建破烂边框的实现:
public class Test {
public static void main(String[] args) throws IOException {
BufferedImage mask = loadImage("e:\mask.png");
BufferedImage img = loadImage("e:\1.png");
int width = mask.getWidth();
int height = mask.getHeight();
BufferedImage processed = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int rgb = mask.getRGB(x,y);
int maskAlpha = alpha(rgb);
int imgColor = img.getRGB(x, y);
if (maskAlpha < 255) {
processed.setRGB(x,y,maskAlpha(imgColor, maskAlpha));
} else {
processed.setRGB(x,y,imgColor);
}
}
}
writeImage(processed, "e:\2.png");
}
static BufferedImage loadImage(String imagePath) {
File file = new File(imagePath);
BufferedImage image = null;
try {
image = ImageIO.read(file);
} catch (IOException e) {
e.printStackTrace();
}
return image;
}
static void writeImage(BufferedImage img,String filePath){
String format = filePath.substring(filePath.indexOf('.')+1);
//Get Picture Format
System.out.println(format);
try {
ImageIO.write(img,format,new File(filePath));
} catch (IOException e) {
e.printStackTrace();
}
}
static int maskAlpha(int rgb, int alpha) {
//strip alpha from original color
int color = (0x00ffffff&rgb);
return color + ((alpha)<<24);
}
static int alpha(int rgb) {
return (0xff&(rgb>>24));
}
static int red(int rgb) {
return (0xff&rgb);
}
static int green(int rgb) {
return (0xff&(rgb>>8));
}
static int blue(int rgb) {
return (0xff&(rgb>>16));
}
}
这里BufferedImage.TYPE_4BYTE_ABGR表示
Represents an image with 8-bit RGBA color components with the colors Blue, Green, and Red stored in 3 bytes and 1 byte of alpha. The image has a ComponentColorModel with alpha. The color data in this image is considered not to be premultiplied with alpha. The byte data is interleaved in a single byte array in the order A, B, G, R from lower to higher byte addresses within each pixel.
表示颜色整数是32位的,按abgr的顺序存放在java中,即前8位是alpha,后8位是r,所以可以得到argb值如下:
int r = (0xff&rgb);
int g = (0xff&(rgb>>8));
int b = (0xff&(rgb>>16));
int a = (0xff&(rgb>>24));
使用 BufferedImage
、Graphics2D
和 AlphaComposite
,您可以这样组合图像:
BufferedImage image = ImageIO.read(new File("image.png"));
BufferedImage mask = ImageIO.read(new File("mask.png"));
BufferedImage composed = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = composed.createGraphics();
try {
g.setComposite(AlphaComposite.Src); // Possibly faster than SrcOver
g.drawImage(image, 0, 0, null);
// Clear out the transparent parts from mask
g.setComposite(AlphaComposite.DstIn);
g.drawImage(mask, 0, 0, null);
}
finally {
g.dispose();
}
if (!ImageIO.write(composed, "PNG", new File("composed.png"))) {
throw new IIOException("Could not write image using PNG format: " + composed);
}
PS:如果您知道您的源图像(上面代码中的image
)包含透明度并且之后不需要原始图像,您可以直接在其上合成遮罩。这将更快并且使用更少的内存,因为您跳过了内存分配和额外的组合。
我不确定为什么在您的特定示例中需要进行检查。如果目标图像不使用 alpha 通道(它都是不透明的),您可以简单地使用源图像的 alpha 通道覆盖数据。
顺便说一句,如果您使用 pixels[]
,一个循环应该可以:
PImage withAlpha;
PImage noAlpha;
void setup(){
size(120, 130);
background(0);
withAlpha = loadImage("8fXFk.png");
noAlpha = loadImage("AOsi0.png");
copyAlphaChannel(withAlpha, noAlpha);
}
void draw(){
background(map(sin(frameCount * 0.1), -1.0, 1.0, 0, 192), 0, 0);
image(withAlpha, 0, 0);
image(noAlpha, 60, 0);
}
void copyAlphaChannel(PImage src, PImage dst){
// quick error check
if(src.width != dst.width || src.height != dst.height){
println(String.format("error, mismatching dimensions src(%d,%d) != dst(%d,%d)",
src.width, src.height, dst.width, dst.height));
return;
}
// load pixel data
src.loadPixels();
dst.loadPixels();
int numPixels = src.pixels.length;
// for each pixel
for(int i = 0 ; i < numPixels; i++){
// extract source alpha
int srcAlpha = (src.pixels[i] >> 24) & 0xFF;
// apply it to the destination image
// src alpha | dst RGB
dst.pixels[i] = srcAlpha << 24 | (dst.pixels[i] & 0xFFFFFF);
}
dst.updatePixels();
}
更新感谢您指出这一点:我错过了这个细节。
PImage
可以有三种格式:
RGB (=1)
ARGB (=2)
ALPHA(=4)
将 RGB
格式 PImage
转换为 ARGB
格式的一种解决方法是应用不透明的 mask()
:
PImage withAlpha;
PImage noAlpha;
void setup(){
size(120, 130);
background(0);
withAlpha = loadImage("8fXFk.png");
noAlpha = loadImage("AOsi0.png");
println("before",withAlpha.format, noAlpha.format, ARGB, RGB); // notice noAlpha's format is RGB
forceAlphaChannel(noAlpha);
println("after",withAlpha.format, noAlpha.format, ARGB, RGB); // notice noAlpha's format is ARGB
copyAlphaChannel(withAlpha, noAlpha);
noAlpha.save("test.png");
}
void draw(){
background(map(sin(frameCount * 0.1), -1.0, 1.0, 0, 192), 0, 0);
image(withAlpha, 0, 0);
image(noAlpha, 60, 0);
}
void forceAlphaChannel(PImage src){
// make an opaque mask
PImage mask = createImage(src.width, src.height, ALPHA);
java.util.Arrays.fill(mask.pixels, color(255));
mask.updatePixels();
// apply the mask force the RGB image into ARGB format
src.mask(mask);
}
void copyAlphaChannel(PImage src, PImage dst){
// quick error check
if(src.width != dst.width || src.height != dst.height){
println(String.format("error, mismatching dimensions src(%d,%d) != dst(%d,%d)",
src.width, src.height, dst.width, dst.height));
return;
}
// load pixel data
src.loadPixels();
dst.loadPixels();
int numPixels = src.pixels.length;
// for each pixel
for(int i = 0 ; i < numPixels; i++){
// extract source alpha
int srcAlpha = (src.pixels[i] >> 24) & 0xFF;
// apply it to the destination image
// src alpha | dst RGB
dst.pixels[i] = srcAlpha << 24 | (dst.pixels[i] & 0xFFFFFF);
}
dst.updatePixels();
}
由于以上循环多次像素(一次创建蒙版并再次应用它),首先创建一个 ARGB
PImage
可能更有效,然后从一个 PImage
复制 RGB
数据,从另一个 ALPHA
复制数据:
PImage withAlpha;
PImage noAlpha;
void setup(){
size(120, 130);
background(0);
withAlpha = loadImage("8fXFk.png");
noAlpha = loadImage("AOsi0.png");
println("before",withAlpha.format, noAlpha.format, ARGB, RGB); // notice noAlpha's format is RGB
noAlpha = getAlphaChannelCopy(withAlpha, noAlpha);
println("after",withAlpha.format, noAlpha.format, ARGB, RGB); // notice noAlpha's format is ARGB
noAlpha.save("test.png");
}
void draw(){
background(map(sin(frameCount * 0.1), -1.0, 1.0, 0, 192), 0, 0);
image(withAlpha, 0, 0);
image(noAlpha, 60, 0);
}
// copy src alpha and dst rgb into new ARGB PImage
PImage getAlphaChannelCopy(PImage src, PImage dst){
// quick error check
if(src.width != dst.width || src.height != dst.height){
println(String.format("error, mismatching dimensions src(%d,%d) != dst(%d,%d)",
src.width, src.height, dst.width, dst.height));
return null;
}
PImage out = createImage(src.width, src.height, ARGB);
// load pixel data
src.loadPixels();
dst.loadPixels();
out.loadPixels();
int numPixels = src.pixels.length;
// for each pixel
for(int i = 0 ; i < numPixels; i++){
// extract source alpha
int srcAlpha = (src.pixels[i] >> 24) & 0xFF;
// apply it to the destination image
// src alpha | dst RGB
out.pixels[i] = srcAlpha << 24 | (dst.pixels[i] & 0xFFFFFF);
}
out.updatePixels();
return out;
}
(这里唯一的小缺点是您 loadPixels()
三次:每张图片一次。)