在一张图片中寻找透明像素并将相同像素设置为透明,在另一张图片上进行相同操作。

3
我有两张图片。第二张是将某种蒙版应用于第一张的结果。我需要的是获取该蒙版并能够将其应用到其他图片上。
这是两个图片:正常破损
如您所见,第二张图片的边缘是破损的,它们是透明的,而不是白色的。(如果有一种方法可以确定模糊效果,则很好,但这不是必要的)。
我需要的是能够从第一张图片创建出第二张图片。
理论上,我应该创建一个蒙版 - 一张与任何颜色相同大小的图片,每个像素具有透明度为0或255,具体取决于上述第二张图片中相同像素的透明度值。然后我只需将任何输入图像的alpha设置为来自此蒙版的alpha值。
然而,我不知道如何实际操作。我尝试使用BufferedImage在java中进行操作,但它无法工作。当我尝试从所选像素的颜色中获取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"); 
4个回答

1
你可以尝试获取原始图像和破损图像的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"); 


1
你可以使用破损的图像作为其他图像的蒙版。你只需要从蒙版中获取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的意思是

用8位RGBA颜色组分表示图像,其中蓝色、绿色和红色存储在3个字节中,alpha存储在1个字节中。该图像具有带alpha的ComponentColorModel。此图像中的颜色数据被认为未经过alpha预乘。字节数据以每个像素内从低到高字节地址的顺序A、B、G、R交错存储在单个字节数组中。

这意味着颜色整数是32位的,在Java中按abgr的顺序存储,即alpha是前8位,r是最后8位,因此可以按以下方式获取argb值:

int r = (0xff&rgb);
int g = (0xff&(rgb>>8));
int b = (0xff&(rgb>>16));
int a = (0xff&(rgb>>24));

1
它起作用了。非常感谢您的代码和解释,现在我明白了。我想问题是我没有指定BufferedImage.TYPE_4BYTE_ABGR,所以Alpha始终为255。 - Jack
1
好的回答!只是想指出有一个更简单的方法,使用Graphics2DAlphaComposite。只需创建一个带透明度的新图像,将您的图像绘制到其中,使用DST_IN规则,并在其上绘制您的掩码图像(以更改边缘的透明度)。 - Harald K

1
使用 BufferedImageGraphics2DAlphaComposite,您可以像这样组合图像:
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)包含透明度并且之后不需要原始图像,则可以直接在其上合成遮罩。这样会更快,使用的内存更少,因为您跳过了内存分配和额外合成的步骤。


0

我不确定为什么在你的特定示例中需要检查。如果目标图像不使用 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();
}

two images that appear identical: the second one has it's alpha channel (tattered edges) copied from the first

更新:感谢您指出这一点:我错过了这个细节。

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();
}

由于上述代码多次循环像素(一次用于创建掩码,另一次用于应用掩码),因此更有效的方法可能是首先创建一个ARGBPImage,然后从一个PImage复制RGB数据,从另一个PImage复制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():每个图像一次。


它确实在屏幕上都加载了带透明度的图像。但是当我在setup()末尾添加noAlpha.save("out.png")时,由于某种原因它保存为没有透明度的图像。 - Jack
1
@Jack 确实!谢谢你指出来。请查看上面更新的内容。 - George Profenza

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接