【问题标题】:Finding transparent pixels on an image and making same pixels transparent on another image在图像上查找透明像素并使相同像素在另一个图像上透明
【发布时间】:2021-06-30 17:37:42
【问题描述】:

我有两张图片。第二个是对第一个应用某种掩码的结果。我需要的是获得该蒙版并能够将其应用于其他图像。 这是两张图片:normaltattered

如您所见,第二个边缘有破烂,它们是透明的,而不是白色的。 (还有某种模糊,如果有办法我可以找出它到底是什么模糊,那就太好了,但这里不是真的必要)

我需要的是能够从第一个图像创建第二个图像。

理论上,我应该创建一个蒙版 - 一个大小相同的图像,任何颜色,每个像素的透明度为 0 或 255,具体取决于上面第二张图像中相同像素的透明度值。然后我可以将任何输入图像的像素的 alpha 设置为这个掩码的 alpha 值。

但是,我不知道如何实际操作。我在 java 中使用 BufferedImage 进行了尝试,但是它不起作用。当我尝试从所选像素的颜色中获取 Alpha 时,它始终为 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"); 

【问题讨论】:

    标签: java image processing png transparency


    【解决方案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 个字节和 1 个字节的 alpha 中。图片 有一个带有 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));
    

    【讨论】:

    • 成功了。非常感谢您的代码和解释,我现在明白了。我想问题是我没有指定 BufferedImage.TYPE_4BYTE_ABGR,所以 alpha 总是 255。
    • 好答案!就像指出有一个更简单的方法,using Graphics2D and the AlphaComposite class。只需创建一个具有透明度的新图像,将您的图像绘制到其中,使用DST_IN 规则并绘制您的蒙版图像(以更改边缘的透明度)。
    【解决方案2】:

    您可以尝试区分原始图像和破烂图像的 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"); 
    
    

    【讨论】:

      【解决方案3】:

      使用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)包含透明度并且之后不需要原始图像,您可以直接在其上合成蒙版。由于您跳过了内存分配和额外的组合,这将更快并且使用更少的内存。

      【讨论】:

        【解决方案4】:

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

        由于上述循环遍历像素很多次(一次创建蒙版,然后再次应用它),首先创建ARGBPImage,然后复制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 确实!感谢您指出了这一点。请参阅上面的更新
        猜你喜欢
        • 1970-01-01
        • 2011-07-20
        • 2013-12-15
        • 1970-01-01
        • 2018-10-11
        • 2016-03-12
        • 2013-09-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多