text: /* Copyright 2012 Mozilla Foundation
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  import { DecodeStream } from "./decode_stream.js";
  import { Dict } from "./primitives.js";
  import { FormatError } from "../shared/util.js";
  
  class PredictorStream extends DecodeStream {
    constructor(str, maybeLength, params) {
      super(maybeLength);
  
      if (!(params instanceof Dict)) {
        return str; // no prediction
      }
      const predictor = (this.predictor = params.get("Predictor") || 1);
  
      if (predictor <= 1) {
        return str; // no prediction
      }
      if (predictor !== 2 && (predictor < 10 || predictor > 15)) {
        throw new FormatError(`Unsupported predictor: ${predictor}`);
      }
  
      if (predictor === 2) {
        this.readBlock = this.readBlockTiff;
      } else {
        this.readBlock = this.readBlockPng;
      }
  
      this.str = str;
      this.dict = str.dict;
  
      const colors = (this.colors = params.get("Colors") || 1);
      const bits = (this.bits = params.get("BPC", "BitsPerComponent") || 8);
      const columns = (this.columns = params.get("Columns") || 1);
  
      this.pixBytes = (colors * bits + 7) >> 3;
      this.rowBytes = (columns * colors * bits + 7) >> 3;
  
      return this;
    }
  
    readBlockTiff() {
      const rowBytes = this.rowBytes;
  
      const bufferLength = this.bufferLength;
      const buffer = this.ensureBuffer(bufferLength + rowBytes);
  
      const bits = this.bits;
      const colors = this.colors;
  
      const rawBytes = this.str.getBytes(rowBytes);
      this.eof = !rawBytes.length;
      if (this.eof) {
        return;
      }
  
      let inbuf = 0,
        outbuf = 0;
      let inbits = 0,
        outbits = 0;
      let pos = bufferLength;
      let i;
  
      if (bits === 1 && colors === 1) {
        // Optimized version of the loop in the "else"-branch
        // for 1 bit-per-component and 1 color TIFF images.
        for (i = 0; i < rowBytes; ++i) {
          let c = rawBytes[i] ^ inbuf;
          c ^= c >> 1;
          c ^= c >> 2;
          c ^= c >> 4;
          inbuf = (c & 1) << 7;
          buffer[pos++] = c;
        }
      } else if (bits === 8) {
        for (i = 0; i < colors; ++i) {
          buffer[pos++] = rawBytes[i];
        }
        for (; i < rowBytes; ++i) {
          buffer[pos] = buffer[pos - colors] + rawBytes[i];
          pos++;
        }
      } else if (bits === 16) {
        const bytesPerPixel = colors * 2;
        for (i = 0; i < bytesPerPixel; ++i) {
          buffer[pos++] = rawBytes[i];
        }
        for (; i < rowBytes; i += 2) {
          const sum =
            ((rawBytes[i] & 0xff) << 8) +
            (rawBytes[i + 1] & 0xff) +
            ((buffer[pos - bytesPerPixel] & 0xff) << 8) +
            (buffer[pos - bytesPerPixel + 1] & 0xff);
          buffer[pos++] = (sum >> 8) & 0xff;
          buffer[pos++] = sum & 0xff;
        }
      } else {
        const compArray = new Uint8Array(colors + 1);
        const bitMask = (1 << bits) - 1;
        let j = 0,
          k = bufferLength;
        const columns = this.columns;
        for (i = 0; i < columns; ++i) {
          for (let kk = 0; kk < colors; ++kk) {
            if (inbits < bits) {
              inbuf = (inbuf << 8) | (rawBytes[j++] & 0xff);
              inbits += 8;
            }
            compArray[kk] =
              (compArray[kk] + (inbuf >> (inbits - bits))) & bitMask;
            inbits -= bits;
            outbuf = (outbuf << bits) | compArray[kk];
            outbits += bits;
            if (outbits >= 8) {
              buffer[k++] = (outbuf >> (outbits - 8)) & 0xff;
              outbits -= 8;
            }
          }
        }
        if (outbits > 0) {
          buffer[k++] =
            (outbuf << (8 - outbits)) + (inbuf & ((1 << (8 - outbits)) - 1));
        }
      }
      this.bufferLength += rowBytes;
    }
  
    readBlockPng() {
      const rowBytes = this.rowBytes;
      const pixBytes = this.pixBytes;
  
      const predictor = this.str.getByte();
      const rawBytes = this.str.getBytes(rowBytes);
      this.eof = !rawBytes.length;
      if (this.eof) {
        return;
      }
  
      const bufferLength = this.bufferLength;
      const buffer = this.ensureBuffer(bufferLength + rowBytes);
  
      let prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength);
      if (prevRow.length === 0) {
        prevRow = new Uint8Array(rowBytes);
      }
  
      let i,
        j = bufferLength,
        up,
        c;
      switch (predictor) {
        case 0:
          for (i = 0; i < rowBytes; ++i) {
            buffer[j++] = rawBytes[i];
          }
          break;
        case 1:
          for (i = 0; i < pixBytes; ++i) {
            buffer[j++] = rawBytes[i];
          }
          for (; i < rowBytes; ++i) {
            buffer[j] = (buffer[j - pixBytes] + rawBytes[i]) & 0xff;
            j++;
          }
          break;
        case 2:
          for (i = 0; i < rowBytes; ++i) {
            buffer[j++] = (prevRow[i] + rawBytes[i]) & 0xff;
          }
          break;
        case 3:
          for (i = 0; i < pixBytes; ++i) {
            buffer[j++] = (prevRow[i] >> 1) + rawBytes[i];
          }
          for (; i < rowBytes; ++i) {
            buffer[j] =
              (((prevRow[i] + buffer[j - pixBytes]) >> 1) + rawBytes[i]) & 0xff;
            j++;
          }
          break;
        case 4:
          // we need to save the up left pixels values. the simplest way
          // is to create a new buffer
          for (i = 0; i < pixBytes; ++i) {
            up = prevRow[i];
            c = rawBytes[i];
            buffer[j++] = up + c;
          }
          for (; i < rowBytes; ++i) {
            up = prevRow[i];
            const upLeft = prevRow[i - pixBytes];
            const left = buffer[j - pixBytes];
            const p = left + up - upLeft;
  
            let pa = p - left;
            if (pa < 0) {
              pa = -pa;
            }
            let pb = p - up;
            if (pb < 0) {
              pb = -pb;
            }
            let pc = p - upLeft;
            if (pc < 0) {
              pc = -pc;
            }
  
            c = rawBytes[i];
            if (pa <= pb && pa <= pc) {
              buffer[j++] = left + c;
            } else if (pb <= pc) {
              buffer[j++] = up + c;
            } else {
              buffer[j++] = upLeft + c;
            }
          }
          break;
        default:
          throw new FormatError(`Unsupported predictor: ${predictor}`);
      }
      this.bufferLength += rowBytes;
    }
  }
  
  export { PredictorStream };
  