qoiDecode function

QoiFile qoiDecode(
  1. Uint8List bytes,
  2. PixelFormat pixelFormat
)

Decode Uint8List to a QoiFile with the given PixelFormat as an output pixel format.

PixelFormat can be any of PixelFormat.RGB (0xRRGGBB), PixelFormat.ARGB (0xAARRGGBB) or PixelFormat.RGBA (0xRRGGBBAA).

Returns a QoiFile with information such as width, height, etc.

Implementation

QoiFile qoiDecode(Uint8List bytes, PixelFormat pixelFormat) {
  int readIndex = 0;

  int readByte() {
    return bytes[readIndex++];
  }

  int readInt32() {
    return readByte() << 24 | readByte() << 16 | readByte() << 8 | readByte();
  }

  if (bytes.length < 23 || readByte() != 113 || readByte() != 111 || readByte() != 105 || readByte() != 102) {
    throw "Invalid QOI file";
  }

  if (bytes[bytes.length - 1] != 1 || bytes[bytes.length - 2] != 0
      ||  bytes[bytes.length - 3] != 0||  bytes[bytes.length - 4] != 0
      ||  bytes[bytes.length - 5] != 0||  bytes[bytes.length - 6] != 0
      ||  bytes[bytes.length - 7] != 0||  bytes[bytes.length - 8] != 0) {
    throw "Invalid QOI file";
  }

  int width = readInt32();
  int height = readInt32();

  if (width <= 0 || height <= 0 || height > 2147483647 / width) {
    throw "Invalid width / height";
  }

  int channels = readByte();
  bool hasAlpha = false;

  if (channels == 3) {
  }
  else if (channels == 4) {
    hasAlpha = true;
  }
  else {
    throw "Invalid channels";
  }

  int colorSpace = readByte();
  bool linearColorSpace = false;

  if (colorSpace == 0) {
  }
  else if (colorSpace == 1) {
    linearColorSpace = true;
  }
  else {
    throw "Invalid color space";
  }

  final outChannels = pixelFormat == PixelFormat.RGB ? 3 : 4;
  final outBytes = Uint8List(width * height * outChannels);
  int writeIndex = 0;

  final pixelIndex = Uint32List.fromList(List.filled(64, 0));
  int prevPixelR = 0;
  int prevPixelG = 0;
  int prevPixelB = 0;
  int prevPixelA = 255;

  void writeColor(int r, int g, int b, int a) {
    if (pixelFormat == PixelFormat.RGBA) {
      outBytes[writeIndex++] = r & 0x000000FF;
      outBytes[writeIndex++] = g & 0x000000FF;
      outBytes[writeIndex++] = b & 0x000000FF;
      outBytes[writeIndex++] = a & 0x000000FF;
    }
    else if (pixelFormat == PixelFormat.ARGB) {
      outBytes[writeIndex++] = a & 0x000000FF;
      outBytes[writeIndex++] = r & 0x000000FF;
      outBytes[writeIndex++] = g & 0x000000FF;
      outBytes[writeIndex++] = b & 0x000000FF;
    }
    else {
      outBytes[writeIndex++] = r & 0x000000FF;
      outBytes[writeIndex++] = g & 0x000000FF;
      outBytes[writeIndex++] = b & 0x000000FF;
    }
  }

  while (readIndex < bytes.length - 8) {
    int op = readByte();

    if (op == _QOI_OP_RGB) {
      int r = readByte();
      int g = readByte();
      int b = readByte();
      int a = prevPixelA;

      writeColor(r, g, b, a);

      prevPixelR = r;
      prevPixelG = g;
      prevPixelB = b;
      prevPixelA = a;
      pixelIndex[(r * 3 + g * 5 + b * 7 + a * 11) % 64] = r << 24 | g << 16 | b << 8 | a;
    }
    else if (op == _QOI_OP_RGBA) {
      int r = readByte();
      int g = readByte();
      int b = readByte();
      int a = readByte();

      writeColor(r, g, b, a);

      prevPixelR = r;
      prevPixelG = g;
      prevPixelB = b;
      prevPixelA = a;
      pixelIndex[(r * 3 + g * 5 + b * 7 + a * 11) % 64] = r << 24 | g << 16 | b << 8 | a;
    }
    else if (op >> 6 == _QOI_OP_INDEX) {
      int index = op & 0x3F;
      int color = pixelIndex[index];

      int r = (color & 0xFF000000) >> 24;
      int g = (color & 0x00FF0000) >> 16;
      int b = (color & 0x0000FF00) >> 8;
      int a = (color & 0x000000FF);

      writeColor(r, g, b, a);

      prevPixelR = r;
      prevPixelG = g;
      prevPixelB = b;
      prevPixelA = a;
    }
    else if (op >> 6 == _QOI_OP_DIFF) {
      int r = (prevPixelR + ((op & 0x30) >> 4) - 2) % 256;
      int g = (prevPixelG + ((op & 0x0C) >> 2) - 2) % 256;
      int b = (prevPixelB + (op & 0x03) - 2) % 256;
      int a = prevPixelA;

      writeColor(r, g, b, a);

      prevPixelR = r;
      prevPixelG = g;
      prevPixelB = b;
      prevPixelA = a;
      pixelIndex[(r * 3 + g * 5 + b * 7 + a * 11) % 64] = r << 24 | g << 16 | b << 8 | a;
    }
    else if (op >> 6 == _QOI_OP_LUMA) {
      int byte = readByte();

      int dg = (op & 0x3F) - 32;
      int dr = ((byte & 0xF0) >> 4) + dg;
      int db = (byte & 0x0F) + dg;

      int r = (prevPixelR + dr - 8) % 256;
      int g = (prevPixelG + dg) % 256;
      int b = (prevPixelB + db - 8) % 256;
      int a = prevPixelA;

      writeColor(r, g, b, a);

      prevPixelR = r;
      prevPixelG = g;
      prevPixelB = b;
      prevPixelA = a;
      pixelIndex[(r * 3 + g * 5 + b * 7 + a * 11) % 64] = r << 24 | g << 16 | b << 8 | a;
    }
    else if (op >> 6 == _QOI_OP_RUN) {
      int run = (op & 0x3F) + 1;

      for (int a = 0; a < run; a++) {
        writeColor(prevPixelR, prevPixelG, prevPixelB, prevPixelA);
      }
    }
  }

  return QoiFile(width, height, outBytes, hasAlpha, linearColorSpace);
}