foliojs / png.js

A (animated) PNG decoder in JavaScript for the HTML5 canvas element and Node.js
http://devongovett.github.com/png.js
MIT License
489 stars 92 forks source link

Implemented interlacing support for Node.js (in JavaScript, not CoffeeScript). #8

Open eddyb opened 11 years ago

eddyb commented 11 years ago

I wanted to make sure this works, so I didn't try to fiddle with png-node.coffee, I modified the .js output.

    PNG.prototype.decodePixels = function(fn) {
      var _this = this;
      return zlib.inflate(this.imgData, function(err, data) {
        if (err) throw err;
        var pixelBytes = _this.pixelBitlength / 8;
        var fullPixels = new Buffer(_this.width * _this.height * pixelBytes);
        var pos = 0;
        function pass(x0, y0, dx, dy) {
          var byte, c, col, i, left, p, pa, paeth, pb, pc, pixels, row, scanlineLength, upper, upperLeft;
          var w = Math.ceil((_this.width - x0) / dx), h = Math.ceil((_this.height - y0) / dy);
          var isFull = _this.width == w && _this.height == h;
          scanlineLength = pixelBytes * w;
          pixels = isFull ? fullPixels : new Buffer(scanlineLength * h);
          row = 0;
          c = 0;
          while (row < h && pos < data.length) {
            switch (data[pos++]) {
              case 0:
                for (i = 0; i < scanlineLength; i += 1) {
                  pixels[c++] = data[pos++];
                }
                break;
              case 1:
                for (i = 0; i < scanlineLength; i += 1) {
                  byte = data[pos++];
                  left = i < pixelBytes ? 0 : pixels[c - pixelBytes];
                  pixels[c++] = (byte + left) % 256;
                }
                break;
              case 2:
                for (i = 0; i < scanlineLength; i += 1) {
                  byte = data[pos++];
                  col = (i - (i % pixelBytes)) / pixelBytes;
                  upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)];
                  pixels[c++] = (upper + byte) % 256;
                }
                break;
              case 3:
                for (i = 0; i < scanlineLength; i += 1) {
                  byte = data[pos++];
                  col = (i - (i % pixelBytes)) / pixelBytes;
                  left = i < pixelBytes ? 0 : pixels[c - pixelBytes];
                  upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)];
                  pixels[c++] = (byte + Math.floor((left + upper) / 2)) % 256;
                }
                break;
              case 4:
                for (i = 0; i < scanlineLength; i += 1) {
                  byte = data[pos++];
                  col = (i - (i % pixelBytes)) / pixelBytes;
                  left = i < pixelBytes ? 0 : pixels[c - pixelBytes];
                  if (row === 0) {
                    upper = upperLeft = 0;
                  } else {
                    upper = pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)];
                    upperLeft = col && pixels[(row - 1) * scanlineLength + (col - 1) * pixelBytes + (i % pixelBytes)];
                  }
                  p = left + upper - upperLeft;
                  pa = Math.abs(p - left);
                  pb = Math.abs(p - upper);
                  pc = Math.abs(p - upperLeft);
                  if (pa <= pb && pa <= pc) {
                    paeth = left;
                  } else if (pb <= pc) {
                    paeth = upper;
                  } else {
                    paeth = upperLeft;
                  }
                  pixels[c++] = (byte + paeth) % 256;
                }
                break;
              default:
                throw new Error("Invalid filter algorithm: " + data[pos - 1]);
            }
            if (!isFull) {
              var fullPos = ((y0 + row * dy) * _this.width + x0) * pixelBytes;
              var partPos = row * scanlineLength;
              for (i = 0; i < w; i += 1) {
                for (var j = 0; j < pixelBytes; j += 1)
                  fullPixels[fullPos++] = pixels[partPos++];
                fullPos += (dx - 1) * pixelBytes;
              }
            }
            row++;
          }
        }
        if (_this.interlaceMethod == 1) {
          /*
            1 6 4 6 2 6 4 6
            7 7 7 7 7 7 7 7
            5 6 5 6 5 6 5 6
            7 7 7 7 7 7 7 7
            3 6 4 6 3 6 4 6
            7 7 7 7 7 7 7 7
            5 6 5 6 5 6 5 6
            7 7 7 7 7 7 7 7
          */
          pass(0, 0, 8, 8); // 1
          /* NOTE these seem to follow the pattern:
           * pass(x, 0, 2*x, 2*x);
           * pass(0, x,   x, 2*x);
           * with x being 4, 2, 1.
           */
          pass(4, 0, 8, 8); // 2
          pass(0, 4, 4, 8); // 3

          pass(2, 0, 4, 4); // 4
          pass(0, 2, 2, 4); // 5

          pass(1, 0, 2, 2); // 6
          pass(0, 1, 1, 2); // 7
        } else
          pass(0, 0, 1, 1);
        return fn(fullPixels);
      });
    };
mgartner commented 8 years ago

@devongovett any way we can get this support for interlaced PNGs merged in? I need to support interlaced PNGs when using pdfkit. I verified that the above go works well, but I'm not familiar enough with CoffeeScript to easily translate this and create a PR.

photopea commented 7 years ago

Hi, I wrote my own PNG parser, which supports interlacing: https://github.com/photopea/UPNG.js .

fluke commented 4 years ago

Is it possible to merge this in?

photopea commented 4 years ago

@kartikluke Why don't you just use UPNG.js instead? It is very small and our custom ZLIB decoder is even faster than pako.js (ZLIB library itself).

fluke commented 4 years ago

@photopea What is the equivalent in UPNG of:

You can also call PNG.load if you want to load the PNG (but not decode the pixels) synchronously. If you already have the PNG data in a buffer, simply use new PNG(buffer).

fluke commented 4 years ago

This method: https://github.com/devongovett/png.js/blob/master/png.js#L61

photopea commented 4 years ago

If you have a file in an ArrayBuffer, just call UPNG.decode(arrayBuffer).

photopea commented 4 years ago

You can learn how to use UPNG.js at its github page. I would probably not do anything else, if I had to explain how to use UPNG.js to each one of thousands of UPNG users :D

I wonder that people still use libraries, which have not been maintained by the author for seven years, even though there seem to be things that need maintenance (interlacing support).

blikblum commented 4 years ago

The implementation in original post works only for some file types.

See https://github.com/blikblum/pdfkit-interlaced-png example. When replacing the png.js by a patched version only images/interlaced-rgb-alpha-8bit.png is correctly rendered

Any hints on how to fix it is appreciated

photopea commented 4 years ago

Guys, just switch to UPNG.js, it has more GitHub stars, if that is what you are looking for :D

blikblum commented 4 years ago

The implementation in original post works only for some file types.

In fact the implementation is correct. Just need some code in pdfkit side