【问题标题】:How to convert a 24 Bit PNG to 3 Bit PNG using Floyd–Steinberg dithering?如何使用 Floyd–Steinberg 抖动将 24 位 PNG 转换为 3 位 PNG?
【发布时间】:2011-08-21 20:42:47
【问题描述】:

如何使用Floyd–Steinberg dithering 将 24 位 PNG 转换为 3 位 PNG? java.awt.image.BufferedImage 应该用于获取和设置 RGB 值。

在 wikipedia 上,给出了如何将 16 位图像转换为 8 位图像的示例:

find_closest_palette_color(oldpixel) = (oldpixel + 128) / 256

基于此,对于如何拟合上面的示例以实现目标有什么想法吗?

【问题讨论】:

    标签: java image-processing


    【解决方案1】:

    使用image.getRGB(x, y)image.setRGB(x, y, color) 并使用wikipedia article 中的pseudocode。请注意,wiki 上的代码并没有说明如何“减”、“加”和“乘”颜色。 (下面的T3 类处理“颜色”操作。)

    下面的代码会产生这个截图:

    class Test {
      private static BufferedImage floydSteinbergDithering(BufferedImage img) {
    
        C3[] palette = new C3[] {
            new C3(  0,   0,   0),
            new C3(  0,   0, 255),
            new C3(  0, 255,   0),
            new C3(  0, 255, 255),
            new C3(255,   0,   0),
            new C3(255,   0, 255),
            new C3(255, 255,   0),
            new C3(255, 255, 255)
        };
    
        int w = img.getWidth();
        int h = img.getHeight();
    
        C3[][] d = new C3[h][w];
    
        for (int y = 0; y < h; y++) 
          for (int x = 0; x < w; x++) 
            d[y][x] = new C3(img.getRGB(x, y));
    
        for (int y = 0; y < img.getHeight(); y++) {
          for (int x = 0; x < img.getWidth(); x++) {
    
            C3 oldColor = d[y][x];
            C3 newColor = findClosestPaletteColor(oldColor, palette);
            img.setRGB(x, y, newColor.toColor().getRGB());
    
            C3 err = oldColor.sub(newColor);
    
            if (x+1 < w)         d[y  ][x+1] = d[y  ][x+1].add(err.mul(7./16));
            if (x-1>=0 && y+1<h) d[y+1][x-1] = d[y+1][x-1].add(err.mul(3./16));
            if (y+1 < h)         d[y+1][x  ] = d[y+1][x  ].add(err.mul(5./16));
            if (x+1<w && y+1<h)  d[y+1][x+1] = d[y+1][x+1].add(err.mul(1./16));
          }
        }
    
        return img;
      }
    
      private static C3 findClosestPaletteColor(C3 c, C3[] palette) {
        C3 closest = palette[0];
    
        for (C3 n : palette) 
          if (n.diff(c) < closest.diff(c))
            closest = n;
    
        return closest;
      }
    
      public static void main(String[] args) throws IOException {
    
        final BufferedImage normal  = ImageIO.read(new URL("http://upload.wikimedia.org/wikipedia/en/2/24/Lenna.png")).getSubimage(100, 100, 300, 300);
        final BufferedImage dietered = floydSteinbergDithering(ImageIO.read(new URL("http://upload.wikimedia.org/wikipedia/en/2/24/Lenna.png"))).getSubimage(100, 100, 300, 300);
    
        JFrame frame = new JFrame("Test");
        frame.setLayout(new GridLayout(1, 2));
    
        frame.add(new JComponent() {
          @Override
          protected void paintComponent(Graphics g) {
             super.paintComponent(g);
             g.drawImage(normal, 0, 0, this);
          }
        });
        frame.add(new JComponent() {
          @Override
          protected void paintComponent(Graphics g) {
             super.paintComponent(g);
             g.drawImage(dietered, 0, 0, this);
          }
        });
    
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 400);
        frame.setVisible(true);
      }
    
    
      static class C3 {
        int r, g, b;
    
        public C3(int c) {
          Color color = new Color(c);
          this.r = color.getRed();
          this.g = color.getGreen();
          this.b = color.getBlue();
        }
        public C3(int r, int g, int b) {
          this.r = r;
          this.g = g;
          this.b = b;
        }
    
        public C3 add(C3 o) {
          return new C3(r + o.r, g + o.g, b + o.b);
        }
        public C3 sub(C3 o) {
          return new C3(r - o.r, g - o.g, b - o.b);
        }
        public C3 mul(double d) {
          return new C3((int) (d * r), (int) (d * g), (int) (d * b));
        }
        public int diff(C3 o) {
          return Math.abs(r - o.r) +  Math.abs(g - o.g) +  Math.abs(b - o.b);
        }
    
        public int toRGB() {
          return toColor().getRGB();
        }
        public Color toColor() {
          return new Color(clamp(r), clamp(g), clamp(b));
        }
        public int clamp(int c) {
          return Math.max(0, Math.min(255, c));
        }
      }
    }
    

    【讨论】:

    • 此代码非常特定于 3 位转换,不适用于任何其他位深度,并且不允许使用优化的调色板。仍然很高兴看到带有示例的工作代码。
    • 关于抖动,大多数人没有意识到的另一件事是您必须考虑伽马曲线。值 128 将产生一半时间 255 和 0 另一半,但这应该是输入值 186 的正确输出。
    • @Mark:添加了调色板..你可能是对的,我刚刚实现了维基百科版本!
    • 最后一点,通过将错误添加回图像中,当总和大于 255 或小于 0 时,您将面临异常风险。以免您认为我抱怨太多,我已经给出你 +1。
    • 这个实现缺少我相信的diff 方法。
    【解决方案2】:

    源代码需要静态类 C3 中缺少的方法“diff”。 否则,它不会编译或工作。

    这是缺少的 diff 方法:

    public int diff(C3 o) {
        int Rdiff = o.r - this.r;
        int Gdiff = o.g - this.g;
        int Bdiff = o.b - this.b;
        int distanceSquared = Rdiff*Rdiff + Gdiff*Gdiff + Bdiff*Bdiff;
        return distanceSquared;
    }
    

    【讨论】:

      【解决方案3】:
      【解决方案4】:

      顺便说一下,这里是视频中 Floyd-Steinberg 的代码和现场演示! :)

      http://blog.ivank.net/floyd-steinberg-dithering-in-javascript.html

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-02-17
        • 2013-04-22
        • 2011-08-10
        • 1970-01-01
        • 2016-07-27
        • 2016-09-16
        • 2015-10-17
        相关资源
        最近更新 更多