简体   繁体   English

在图像上查找透明像素并使相同像素在另一个图像上透明

[英]Finding transparent pixels on an image and making same pixels transparent on another image

I have two images.我有两张图片。 The second one is the result of applying some sort of mask to the first one.第二个是对第一个应用某种掩码的结果。 What I need is to get that mask and be able to apply it to other images.我需要的是获得该蒙版并能够将其应用于其他图像。 Here are the two images: normal , tattered .这是两张图片: normaltattered

As you can see, the second one has tattered edges, they are transparent, not white.如您所见,第二个边缘破烂,它们是透明的,而不是白色的。 (There's also some kind of blur going on, if there's a way I can find out what blur it is exactly, then great, but it's not really necessary here) (还有某种模糊,如果有办法我可以找出它到底是什么模糊,那就太好了,但这里不是真的必要)

What I need is to be able to create the second image from the first one.我需要的是能够从第一个图像创建第二个图像。

Theoretically, I should create a mask - an image of the same size with any color and each pixel having transparency of either 0 or 255, depending on the transparency value of the same pixel in the second image from above.从理论上讲,我应该创建一个蒙版 - 一个具有任何颜色的相同大小的图像,并且每个像素的透明度为 0 或 255,具体取决于上方第二个图像中相同像素的透明度值。 Then I can just set alpha of pixels of any input image to alpha values from this mask.然后我可以将任何输入图像的像素的 alpha 设置为这个掩码的 alpha 值。

However, I have no idea how to do it practically.但是,我不知道如何实际操作。 I tried it in java using BufferedImage, however, it does not work.我在 java 中使用 BufferedImage 进行了尝试,但是它不起作用。 When I try to getAlpha from the color of the selected pixel, it is always 255, even for pixels which are supposed to be transparent.当我尝试从所选像素的颜色中获取 Alpha 时,它始终为 255,即使对于应该是透明的像素也是如此。 I did manage to get alpha values in Processing (they are actually not just 0 or 255, lots of values inbetween), however, when I try to apply this value to a new image and save it, it saves as fully opaque image, when I load it, alpha values are all 255.我确实设法在处理中获得了 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"); 

You could try taking the difference between the alpha channels of the original image and the tattered image.您可以尝试获取原始图像和破烂图像的 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"); 

You can use the tattered image as mask for other images as well.您也可以将破烂的图像用作其他图像的蒙版。 You just need the alpha information from the mask.您只需要掩码中的 alpha 信息。
Implementation of creating tattered borders using BufferedImage:使用 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));
    }
}

Here BufferedImage.TYPE_4BYTE_ABGR means这里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.表示具有 8 位 RGBA 颜色分量的图像,其中 colors 蓝色、绿色和红色存储在 3 个字节和 1 个 alpha 字节中。 The image has a ComponentColorModel with alpha.该图像有一个带有 alpha 的 ComponentColorModel。 The color data in this image is considered not to be premultiplied with alpha.此图像中的颜色数据被认为没有与 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.字节数据以 A、B、G、R 的顺序在每个像素内从低字节地址到高字节地址交错在单个字节数组中。

That means color integer is 32 bits, which is stored in java in the order of abgr, ie, alpha is the first 8 bits, and r is the last 8 bits, so you can get the argb value as follows: That means color integer is 32 bits, which is stored in java in the order of abgr, ie, alpha is the first 8 bits, and r is the last 8 bits, so you can get the argb value as follows:

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

Using BufferedImage , Graphics2D and AlphaComposite , you can compose your image like this:使用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: If you know that your source image ( image in the code above) contains transparency and don't need the original afterwards, you could compose the mask directly onto it. PS:如果您知道您的源图像(上面代码中的image )包含透明度并且之后不需要原始图像,您可以直接在其上合成蒙版。 This will be faster and use less memory, as you skip the memory allocation and extra composing.这将更快并且使用更少的 memory,因为您跳过了 memory 分配和额外的组合。

I'm not sure why the check is necessary in your particular example.我不确定为什么在您的特定示例中需要进行检查。 If the destination image doesn't use the alpha channel (it's all opaque), you can simply overwrite the data using the source image's alpha channel.如果目标图像不使用 alpha 通道(它都是不透明的),您可以简单地使用源图像的 alpha 通道覆盖数据。

If you're using pixels[] by the way, a single loop should do:顺便说一句,如果您使用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();
}

两个看起来相同的图像:第二个具有从第一个复制的 Alpha 通道(破烂的边缘)

Update Thank you for pointing that out: I've missed this detail.更新感谢您指出这一点:我错过了这个细节。

PImage can have three formats: PImage可以有三种格式:

  • RGB (=1)
  • ARGB (=2)
  • ALPHA(=4)

One workaround to convert an RGB format PImage into ARGB format is to apply an opaque mask() :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();
}

Since the above loops through pixels a bunch of times (once to create the mask and again to apply it) it may be more efficient to create an ARGB PImage in the first place, then copy RGB data from one PImage and ALPHA from another:由于上述循环遍历像素很多次(一次创建掩码并再次应用它),因此首先创建ARGB PImage可能更有效,然后从一个 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;
}

(The only minor downside here is you'd loadPixels() thrice: once per image.) (这里唯一的小缺点是您将loadPixels()三次:每张图像一次。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM