diff --git a/.gitignore b/.gitignore index 639c87c4..b95bd4d5 100644 --- a/.gitignore +++ b/.gitignore @@ -25,5 +25,7 @@ bindings/generated dump.txt tests/fileformats/jpeg/generated tests/fileformats/jpeg/diffs +tests/fileformats/webp/generated +tests/fileformats/webp/diffs *.dylib tmp/ diff --git a/src/pixie.nim b/src/pixie.nim index f2f53e69..e830e082 100644 --- a/src/pixie.nim +++ b/src/pixie.nim @@ -2,14 +2,15 @@ import std/[os, strutils], bumpy, chroma, flatty/binny, vmath, pixie/[common, contexts, fonts, imagebase64, images, internal, paints, paths], - pixie/fileformats/[bmp, gif, jpeg, png, ppm, qoi, svg] + pixie/fileformats/[bmp, gif, jpeg, png, ppm, qoi, svg, webp] export bumpy, chroma, common, contexts, fonts, imagebase64, images, paints, paths, vmath type FileFormat* = enum - PngFormat, BmpFormat, JpegFormat, GifFormat, QoiFormat, PpmFormat + PngFormat, BmpFormat, JpegFormat, GifFormat, QoiFormat, PpmFormat, + WebpFormat converter autoStraightAlpha*(c: ColorRGBX): ColorRGBA {.inline, raises: [].} = ## Convert a premultiplied alpha RGBA to a straight alpha RGBA. @@ -41,6 +42,10 @@ proc decodeImageDimensions*( equalMem(data, ppmSignatures[1].cstring, 2) ): decodePpmDimensions(data, len) + elif len > 12 and + equalMem(data, WebpRiffSignature.cstring, 4) and + equalMem(cast[pointer](cast[uint](data) + 8), WebpSignature.cstring, 4): + decodeWebpDimensions(data, len) else: raise newException(PixieError, "Unsupported image file format") @@ -67,6 +72,9 @@ proc decodeImage*(data: string): Image {.raises: [PixieError].} = decodeQoi(data).convertToImage() elif data.len > 9 and data.readStr(0, 2) in ppmSignatures: decodePpm(data) + elif data.len > 12 and data.readStr(0, 4) == WebpRiffSignature and + data.readStr(8, 4) == WebpSignature: + decodeWebp(data) else: raise newException(PixieError, "Unsupported image file format") @@ -103,6 +111,8 @@ proc encodeImage*( raise newException(PixieError, "Unsupported file format") of PpmFormat: image.encodePpm() + of WebpFormat: + raise newException(PixieError, "Unsupported file format") proc writeFile*(image: Image, filePath: string) {.raises: [PixieError].} = ## Writes an image to a file. @@ -112,6 +122,7 @@ proc writeFile*(image: Image, filePath: string) {.raises: [PixieError].} = of ".jpg", ".jpeg": JpegFormat of ".qoi": QoiFormat of ".ppm": PpmFormat + of ".webp": WebpFormat else: raise newException(PixieError, "Unsupported file extension") diff --git a/src/pixie/fileformats/webp.nim b/src/pixie/fileformats/webp.nim new file mode 100644 index 00000000..10059903 --- /dev/null +++ b/src/pixie/fileformats/webp.nim @@ -0,0 +1,2850 @@ +import chroma, flatty/binny, webp_vp8_tables, ../common, ../images + +# WebP is a RIFF container around VP8 or VP8L image data. +# See: https://developers.google.com/speed/webp/docs/riff_container + +const + WebpRiffSignature* = "RIFF" + WebpSignature* = "WEBP" + WebpVp8Signature* = "VP8 " + WebpVp8LSignature* = "VP8L" + WebpVp8XSignature* = "VP8X" + WebpAlphaSignature* = "ALPH" + WebpAnimSignature* = "ANIM" + WebpAnimFrameSignature* = "ANMF" + WebpIccpSignature* = "ICCP" + WebpExifSignature* = "EXIF" + WebpXmpSignature* = "XMP " + +type + WebpCompression* = enum + UnknownWebpCompression + LossyWebp + LosslessWebp + + WebpChunkKind* = enum + UnknownWebpChunk + Vp8Chunk + Vp8LChunk + Vp8XChunk + AlphaChunk + AnimChunk + AnimFrameChunk + IccpChunk + ExifChunk + XmpChunk + + WebpAlphaInfo* = object + compressionMethod*, filterMethod*, preprocessing*: int + + WebpChunkInfo* = object + kind*: WebpChunkKind + fourcc*: string + offset*, size*: int + + WebpInfo* = ref object + ## Parsed WebP container and bitstream header information. + width*, height*: int + fileSize*: int + compression*: WebpCompression + chunks*: seq[WebpChunkInfo] + + hasVp8X*, hasAlpha*, hasIccp*, hasExif*, hasXmp*, hasAnimation*: bool + losslessAlpha*: bool + vp8Version*: int + vp8ShowFrame*: bool + + vp8Offset*, vp8Size*: int + vp8LOffset*, vp8LSize*: int + alphaOffset*, alphaSize*: int + iccpOffset*, iccpSize*: int + exifOffset*, exifSize*: int + xmpOffset*, xmpSize*: int + + alphaInfo*: WebpAlphaInfo + backgroundColor*: ColorRGBA + loopCount*, frameCount*: int + + LosslessBitReader = ref object + data: string + pos, endPos: int + buffer: uint64 + nbits: int + + HuffmanTree = ref object + single: bool + symbol: uint16 + tableMask: uint16 + primaryTable, secondaryTable: seq[uint16] + + ColorCache = ref object + bits: int + colors: seq[array[4, uint8]] + + HuffmanCodeGroup = array[5, HuffmanTree] + + HuffmanInfo = ref object + xsize: int + colorCache: ColorCache + hasColorCache: bool + image: seq[uint16] + bits, mask: int + groups: seq[HuffmanCodeGroup] + + LosslessTransformKind = enum + PredictorTransform + ColorTransform + SubtractGreenTransform + ColorIndexingTransform + + LosslessTransform = ref object + kind: LosslessTransformKind + sizeBits, tableSize: int + data: seq[uint8] + + LosslessDecoder = ref object + bitReader: LosslessBitReader + transforms: array[4, LosslessTransform] + hasTransform: array[4, bool] + transformOrder: seq[int] + width, height: int + + Vp8BoolDecoder = ref object + data: string + pos, endPos: int + value: uint64 + range: uint32 + bitCount: int + zeroByteAfterEof: bool + + Vp8Segment = object + ydc, yac: int16 + y2dc, y2ac: int16 + uvdc, uvac: int16 + deltaValues: bool + quantizerLevel, loopfilterLevel: int8 + + Vp8MacroBlock = object + bpred: array[16, int] + lumaMode, chromaMode: int + segmentId: uint8 + coeffsSkipped, nonZeroDct: bool + + Vp8PreviousMacroBlock = object + bpred: array[4, int] + complexity: array[9, uint8] + + Vp8Frame = ref object + width, height: int + version, pixelType, filterLevel, sharpnessLevel: uint8 + forDisplay, filterType: bool + ybuf, ubuf, vbuf: seq[uint8] + + Vp8Decoder = ref object + b: Vp8BoolDecoder + mbWidth, mbHeight: int + macroblocks: seq[Vp8MacroBlock] + frame: Vp8Frame + segmentsEnabled, segmentsUpdateMap: bool + segments: array[4, Vp8Segment] + loopFilterAdjustmentsEnabled: bool + refDelta, modeDelta: array[4, int] + partitions: array[8, Vp8BoolDecoder] + numPartitions: int + segmentProbs: array[3, uint8] + tokenProbs: Vp8TokenProbTables + hasProbSkipFalse: bool + probSkipFalse: uint8 + top: seq[Vp8PreviousMacroBlock] + left: Vp8PreviousMacroBlock + topBorderY, leftBorderY: seq[uint8] + topBorderU, leftBorderU: seq[uint8] + topBorderV, leftBorderV: seq[uint8] + +when defined(release): + {.push checks: off.} + +{.push raises: [PixieError].} + +template failInvalid(reason = "unable to load") = + raise newException(PixieError, "Invalid WebP, " & reason) + +const + HuffmanCodesPerMetaCode = 5 + CodeLengthCodes = 19 + MaxAllowedCodeLength = 15 + MaxTableBits = 10 + AlphabetSize = [uint16(256 + 24), 256, 256, 256, 40] + CodeLengthCodeOrder = [ + 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + ] + DistanceMap = [ + (0, 1), (1, 0), (1, 1), (-1, 1), (0, 2), (2, 0), (1, 2), (-1, 2), + (2, 1), (-2, 1), (2, 2), (-2, 2), (0, 3), (3, 0), (1, 3), (-1, 3), + (3, 1), (-3, 1), (2, 3), (-2, 3), (3, 2), (-3, 2), (0, 4), (4, 0), + (1, 4), (-1, 4), (4, 1), (-4, 1), (3, 3), (-3, 3), (2, 4), (-2, 4), + (4, 2), (-4, 2), (0, 5), (3, 4), (-3, 4), (4, 3), (-4, 3), (5, 0), + (1, 5), (-1, 5), (5, 1), (-5, 1), (2, 5), (-2, 5), (5, 2), (-5, 2), + (4, 4), (-4, 4), (3, 5), (-3, 5), (5, 3), (-5, 3), (0, 6), (6, 0), + (1, 6), (-1, 6), (6, 1), (-6, 1), (2, 6), (-2, 6), (6, 2), (-6, 2), + (4, 5), (-4, 5), (5, 4), (-5, 4), (3, 6), (-3, 6), (6, 3), (-6, 3), + (0, 7), (7, 0), (1, 7), (-1, 7), (5, 5), (-5, 5), (7, 1), (-7, 1), + (4, 6), (-4, 6), (6, 4), (-6, 4), (2, 7), (-2, 7), (7, 2), (-7, 2), + (3, 7), (-3, 7), (7, 3), (-7, 3), (5, 6), (-5, 6), (6, 5), (-6, 5), + (8, 0), (4, 7), (-4, 7), (7, 4), (-7, 4), (8, 1), (8, 2), (6, 6), + (-6, 6), (8, 3), (5, 7), (-5, 7), (7, 5), (-7, 5), (8, 4), (6, 7), + (-6, 7), (7, 6), (-7, 6), (8, 5), (7, 7), (-7, 7), (8, 6), (8, 7) + ] + +const + Vp8DcPred = 0 + Vp8VPred = 1 + Vp8HPred = 2 + Vp8TmPred = 3 + Vp8BPred = 4 + + Vp8BDcPred = 0 + Vp8BTmPred = 1 + Vp8BVePred = 2 + Vp8BHePred = 3 + Vp8BLdPred = 4 + Vp8BRdPred = 5 + Vp8BVrPred = 6 + Vp8BVlPred = 7 + Vp8BHdPred = 8 + Vp8BHuPred = 9 + + Vp8Dct0 = 0 + Vp8Dct1 = 1 + Vp8Dct4 = 4 + Vp8DctCat1 = 5 + Vp8DctCat6 = 10 + Vp8DctEob = 11 + +proc checkBounds(data: string, pos, len: int) {.inline.} = + if pos < 0 or len < 0 or pos > data.len or len > data.len - pos: + failInvalid("truncated chunk") + +proc readStrChecked(data: string, pos, len: int): string {.inline.} = + data.checkBounds(pos, len) + data.readStr(pos, len) + +proc readUint8Checked(data: string, pos: int): uint8 {.inline.} = + data.checkBounds(pos, 1) + data.readUint8(pos) + +proc readUint16le(data: string, pos: int): int {.inline.} = + data.checkBounds(pos, 2) + data.readUint16(pos).int + +proc readUint24le(data: string, pos: int): int {.inline.} = + data.checkBounds(pos, 3) + data.readUint8(pos + 0).int or + (data.readUint8(pos + 1).int shl 8) or + (data.readUint8(pos + 2).int shl 16) + +proc readUint32le(data: string, pos: int): uint32 {.inline.} = + data.checkBounds(pos, 4) + data.readUint32(pos) + +proc chunkKind(fourcc: string): WebpChunkKind {.inline.} = + case fourcc + of WebpVp8Signature: Vp8Chunk + of WebpVp8LSignature: Vp8LChunk + of WebpVp8XSignature: Vp8XChunk + of WebpAlphaSignature: AlphaChunk + of WebpAnimSignature: AnimChunk + of WebpAnimFrameSignature: AnimFrameChunk + of WebpIccpSignature: IccpChunk + of WebpExifSignature: ExifChunk + of WebpXmpSignature: XmpChunk + else: UnknownWebpChunk + +proc checkImageSize(width, height: int) = + if width <= 0 or height <= 0: + failInvalid("invalid dimensions") + if width.int64 * height.int64 > uint32.high.int64: + failInvalid("image is too large") + +proc wrapByte(value: int): uint8 {.inline.} = + (value and 0xff).uint8 + +proc subsampleSize(size, bits: int): int {.inline.} = + (size + (1 shl bits) - 1) shr bits + +proc newBitReader(data: string, offset, size: int): LosslessBitReader = + data.checkBounds(offset, size) + LosslessBitReader(data: data, pos: offset, endPos: offset + size) + +proc fill(bitReader: LosslessBitReader) = + while bitReader.nbits <= 56 and bitReader.pos < bitReader.endPos: + bitReader.buffer = bitReader.buffer or + (bitReader.data.readUint8(bitReader.pos).uint64 shl bitReader.nbits) + bitReader.nbits += 8 + inc bitReader.pos + +proc peek(bitReader: LosslessBitReader, bits: int): uint64 {.inline.} = + if bits == 0: + 0 + else: + bitReader.buffer and ((1'u64 shl bits) - 1) + +proc consume(bitReader: LosslessBitReader, bits: int) = + if bitReader.nbits < bits: + failInvalid("corrupt VP8L bitstream") + bitReader.buffer = bitReader.buffer shr bits + bitReader.nbits -= bits + +proc readBits(bitReader: LosslessBitReader, bits: int): uint32 = + if bitReader.nbits < bits: + bitReader.fill() + result = bitReader.peek(bits).uint32 + bitReader.consume(bits) + +proc nextCodeword(codeword, tableSize: uint16): uint16 = + if codeword == tableSize - 1: + return codeword + + var adv = 0 + let diff = codeword xor (tableSize - 1) + for bit in countdown(15, 0): + if (diff and (1'u16 shl bit)) != 0: + adv = bit + break + + let mask = 1'u16 shl adv + return (codeword and (mask - 1)) or mask + +proc buildSingleNode(symbol: uint16): HuffmanTree = + HuffmanTree(single: true, symbol: symbol) + +proc buildTwoNode(zero, one: uint16): HuffmanTree = + HuffmanTree( + tableMask: 1, + primaryTable: @[(1'u16 shl 12) or zero, (1'u16 shl 12) or one] + ) + +proc buildImplicit(codeLengths: seq[uint16]): HuffmanTree = + var + numSymbols = 0 + histogram: array[MaxAllowedCodeLength + 1, int] + + for length in codeLengths: + if length.int > MaxAllowedCodeLength: + failInvalid("invalid VP8L Huffman code") + inc histogram[length.int] + if length != 0: + inc numSymbols + + if numSymbols == 0: + failInvalid("invalid VP8L Huffman code") + if numSymbols == 1: + for symbol, length in codeLengths: + if length != 0: + return buildSingleNode(symbol.uint16) + + var maxLength = MaxAllowedCodeLength + while maxLength > 1 and histogram[maxLength] == 0: + dec maxLength + + var + offsets: array[MaxAllowedCodeLength + 1, int] + codespaceUsed = 0 + offsets[1] = histogram[0] + for i in 1 ..< maxLength: + offsets[i + 1] = offsets[i] + histogram[i] + codespaceUsed = (codespaceUsed shl 1) + histogram[i] + codespaceUsed = (codespaceUsed shl 1) + histogram[maxLength] + if codespaceUsed != (1 shl maxLength): + failInvalid("invalid VP8L Huffman code") + + let + tableBits = min(maxLength, MaxTableBits) + tableSize = 1 shl tableBits + result = HuffmanTree() + result.tableMask = (tableSize - 1).uint16 + result.primaryTable.setLen(tableSize) + + var + nextIndex = offsets + sortedSymbols = newSeq[uint16](codeLengths.len) + for symbol, length in codeLengths: + sortedSymbols[nextIndex[length.int]] = symbol.uint16 + inc nextIndex[length.int] + + var + codeword = 0.uint16 + i = histogram[0] + + let primaryTableBits = tableBits + for length in 1 .. primaryTableBits: + let currentTableEnd = 1 shl length + for _ in 0 ..< histogram[length]: + let symbol = sortedSymbols[i] + inc i + result.primaryTable[codeword.int] = (length.uint16 shl 12) or symbol + codeword = nextCodeword(codeword, currentTableEnd.uint16) + + if length < primaryTableBits: + for j in 0 ..< currentTableEnd: + result.primaryTable[currentTableEnd + j] = result.primaryTable[j] + + if maxLength > primaryTableBits: + var + subtableStart = 0 + subtablePrefix = -1 + let primaryTableMask = (1 shl primaryTableBits) - 1 + + for length in (primaryTableBits + 1) .. maxLength: + let subtableSize = 1 shl (length - primaryTableBits) + for _ in 0 ..< histogram[length]: + let prefix = codeword.int and primaryTableMask + if prefix != subtablePrefix: + subtablePrefix = prefix + subtableStart = result.secondaryTable.len + result.primaryTable[prefix] = (length.uint16 shl 12) or + subtableStart.uint16 + result.secondaryTable.setLen(subtableStart + subtableSize) + + let symbol = sortedSymbols[i] + inc i + result.secondaryTable[ + subtableStart + (codeword.int shr primaryTableBits) + ] = (symbol shl 4) or length.uint16 + codeword = nextCodeword(codeword, (1 shl length).uint16) + + if length < maxLength and + (codeword.int and primaryTableMask) == subtablePrefix: + let oldLen = result.secondaryTable.len + result.secondaryTable.setLen(oldLen + oldLen - subtableStart) + for j in subtableStart ..< oldLen: + result.secondaryTable[oldLen + j - subtableStart] = + result.secondaryTable[j] + result.primaryTable[subtablePrefix] = + ((length + 1).uint16 shl 12) or subtableStart.uint16 + +proc readSymbol(tree: HuffmanTree, bitReader: LosslessBitReader): uint16 = + if tree.single: + return tree.symbol + + bitReader.fill() + let + v = bitReader.buffer.uint16 + entry = tree.primaryTable[(v and tree.tableMask).int] + length = (entry shr 12).int + + if length <= MaxTableBits: + bitReader.consume(length) + return entry and 0x0fff + + let + mask = (1 shl (length - MaxTableBits)) - 1 + secondaryIndex = (entry and 0x0fff).int + + ((v.int shr MaxTableBits) and mask) + secondaryEntry = tree.secondaryTable[secondaryIndex] + bitReader.consume((secondaryEntry and 0x000f).int) + secondaryEntry shr 4 + +proc peekSymbol( + tree: HuffmanTree, bitReader: LosslessBitReader, bits: var int, + symbol: var uint16 +): bool = + if tree.single: + bits = 0 + symbol = tree.symbol + return true + + bitReader.fill() + let + v = bitReader.buffer.uint16 + entry = tree.primaryTable[(v and tree.tableMask).int] + length = (entry shr 12).int + if length <= MaxTableBits: + bits = length + symbol = entry and 0x0fff + return true + +proc insert(colorCache: ColorCache, color: array[4, uint8]) = + let colorU32 = + (color[0].uint32 shl 16) or + (color[1].uint32 shl 8) or + color[2].uint32 or + (color[3].uint32 shl 24) + let index = ( + 0x1e35a7bd'u32 * colorU32 + ) shr (32 - colorCache.bits) + colorCache.colors[index.int] = color + +proc lookup(colorCache: ColorCache, index: int): array[4, uint8] = + if index < 0 or index >= colorCache.colors.len: + failInvalid("corrupt VP8L color cache") + colorCache.colors[index] + +proc getCopyDistance( + bitReader: LosslessBitReader, + prefixCode: uint16 +): int = + if prefixCode < 4: + return prefixCode.int + 1 + let + extraBits = ((prefixCode - 2) shr 1).int + offset = (2 + (prefixCode.int and 1)) shl extraBits + bits = bitReader.peek(extraBits).int + bitReader.consume(extraBits) + offset + bits + 1 + +proc planeCodeToDistance(xsize, planeCode: int): int = + if planeCode > 120: + return planeCode - 120 + + let (xoffset, yoffset) = DistanceMap[planeCode - 1] + return max(1, xoffset + yoffset * xsize) + +proc getHuffIndex(info: HuffmanInfo, x, y: int): int = + if info.bits == 0: + return 0 + let position = (y shr info.bits) * info.xsize + (x shr info.bits) + if position < 0 or position >= info.image.len: + failInvalid("corrupt VP8L meta Huffman image") + info.image[position].int + +proc readHuffmanCodeLengths( + decoder: LosslessDecoder, codeLengthCodeLengths: seq[uint16], + numSymbols: uint16 +): seq[uint16] = + let table = buildImplicit(codeLengthCodeLengths) + + var maxSymbol = + if decoder.bitReader.readBits(1) == 1: + let + lengthNBits = 2 + 2 * decoder.bitReader.readBits(3).int + maxMinusTwo = decoder.bitReader.readBits(lengthNBits).uint16 + if maxMinusTwo > numSymbols - 2: + failInvalid("corrupt VP8L Huffman lengths") + 2 + maxMinusTwo.int + else: + numSymbols.int + + result = newSeq[uint16](numSymbols.int) + var + prevCodeLen = 8.uint16 + symbol = 0 + while symbol < numSymbols.int: + if maxSymbol == 0: + break + dec maxSymbol + + decoder.bitReader.fill() + let codeLen = table.readSymbol(decoder.bitReader) + + if codeLen < 16: + result[symbol] = codeLen + inc symbol + if codeLen != 0: + prevCodeLen = codeLen + else: + let + usePrev = codeLen == 16 + slot = codeLen - 16 + var + extraBits: int + repeatOffset: int + case slot + of 0: + extraBits = 2 + repeatOffset = 3 + of 1: + extraBits = 3 + repeatOffset = 3 + of 2: + extraBits = 7 + repeatOffset = 11 + else: + failInvalid("corrupt VP8L Huffman lengths") + var repeat = decoder.bitReader.readBits(extraBits).int + repeatOffset + if symbol + repeat > numSymbols.int: + failInvalid("corrupt VP8L Huffman lengths") + let length = if usePrev: prevCodeLen else: 0.uint16 + while repeat > 0: + result[symbol] = length + inc symbol + dec repeat + +proc readHuffmanCode( + decoder: LosslessDecoder, alphabetSize: uint16 +): HuffmanTree = + let simple = decoder.bitReader.readBits(1) == 1 + + if simple: + let + numSymbols = decoder.bitReader.readBits(1).int + 1 + isFirst8Bits = decoder.bitReader.readBits(1).int + zeroSymbol = decoder.bitReader.readBits(1 + 7 * isFirst8Bits).uint16 + if zeroSymbol >= alphabetSize: + failInvalid("corrupt VP8L Huffman symbol") + if numSymbols == 1: + result = buildSingleNode(zeroSymbol) + else: + let oneSymbol = decoder.bitReader.readBits(8).uint16 + if oneSymbol >= alphabetSize: + failInvalid("corrupt VP8L Huffman symbol") + result = buildTwoNode(zeroSymbol, oneSymbol) + else: + var codeLengthCodeLengths = newSeq[uint16](CodeLengthCodes) + let numCodeLengths = 4 + decoder.bitReader.readBits(4).int + for i in 0 ..< numCodeLengths: + codeLengthCodeLengths[CodeLengthCodeOrder[i]] = + decoder.bitReader.readBits(3).uint16 + + result = buildImplicit( + decoder.readHuffmanCodeLengths(codeLengthCodeLengths, alphabetSize) + ) + +proc decodeImageData( + decoder: LosslessDecoder, width, height: int, + huffmanInfo: HuffmanInfo, + data: var seq[uint8] +) = + let numValues = width * height + var + index = 0 + nextBlockStart = 0 + tree = huffmanInfo.groups[huffmanInfo.getHuffIndex(0, 0)] + + while index < numValues: + decoder.bitReader.fill() + + if index >= nextBlockStart: + let + x = index mod width + y = index div width + nextBlockStart = min((x or huffmanInfo.mask), width - 1) + y * width + 1 + tree = huffmanInfo.groups[huffmanInfo.getHuffIndex(x, y)] + + if tree[0].single and tree[1].single and tree[2].single and tree[3].single: + let code = tree[0].readSymbol(decoder.bitReader) + if code < 256: + let + n = if huffmanInfo.bits == 0: numValues else: nextBlockStart - index + value = [ + tree[1].readSymbol(decoder.bitReader).uint8, + code.uint8, + tree[2].readSymbol(decoder.bitReader).uint8, + tree[3].readSymbol(decoder.bitReader).uint8 + ] + for i in 0 ..< n: + let dst = (index + i) * 4 + data[dst + 0] = value[0] + data[dst + 1] = value[1] + data[dst + 2] = value[2] + data[dst + 3] = value[3] + if huffmanInfo.hasColorCache: + huffmanInfo.colorCache.insert(value) + index += n + continue + + let code = tree[0].readSymbol(decoder.bitReader) + if code < 256: + let + green = code.uint8 + red = tree[1].readSymbol(decoder.bitReader).uint8 + blue = tree[2].readSymbol(decoder.bitReader).uint8 + alpha = tree[3].readSymbol(decoder.bitReader).uint8 + dst = index * 4 + data[dst + 0] = red + data[dst + 1] = green + data[dst + 2] = blue + data[dst + 3] = alpha + if huffmanInfo.hasColorCache: + huffmanInfo.colorCache.insert([red, green, blue, alpha]) + inc index + elif code < 256 + 24: + let + length = getCopyDistance(decoder.bitReader, code - 256) + distSymbol = tree[4].readSymbol(decoder.bitReader) + distCode = getCopyDistance(decoder.bitReader, distSymbol) + dist = planeCodeToDistance(width, distCode) + if index < dist or numValues - index < length: + failInvalid("corrupt VP8L back reference") + for i in 0 ..< length: + let + src = (index - dist + i) * 4 + dst = (index + i) * 4 + data[dst + 0] = data[src + 0] + data[dst + 1] = data[src + 1] + data[dst + 2] = data[src + 2] + data[dst + 3] = data[src + 3] + if huffmanInfo.hasColorCache: + huffmanInfo.colorCache.insert([ + data[dst + 0], data[dst + 1], data[dst + 2], data[dst + 3] + ]) + index += length + else: + if not huffmanInfo.hasColorCache: + failInvalid("missing VP8L color cache") + let + color = huffmanInfo.colorCache.lookup((code - 280).int) + dst = index * 4 + data[dst + 0] = color[0] + data[dst + 1] = color[1] + data[dst + 2] = color[2] + data[dst + 3] = color[3] + inc index + + var + bits: int + nextCode: uint16 + if index < nextBlockStart and tree[0].peekSymbol( + decoder.bitReader, bits, nextCode + ) and nextCode >= 280: + decoder.bitReader.consume(bits) + let + nextColor = huffmanInfo.colorCache.lookup((nextCode - 280).int) + nextDst = index * 4 + data[nextDst + 0] = nextColor[0] + data[nextDst + 1] = nextColor[1] + data[nextDst + 2] = nextColor[2] + data[nextDst + 3] = nextColor[3] + inc index + +proc decodeImageStream( + decoder: LosslessDecoder, xsize, ysize: int, readMeta: bool, + data: var seq[uint8] +) = + var + colorCache: ColorCache + hasColorCache: bool + numHuffGroups = 1 + huffmanBits = 0 + huffmanXSize = 1 + huffmanYSize = 1 + entropyImage: seq[uint16] + if decoder.bitReader.readBits(1) == 1: + let bits = decoder.bitReader.readBits(4).int + if bits notin 1 .. 11: + failInvalid("invalid VP8L color cache") + hasColorCache = true + colorCache = ColorCache( + bits: bits, + colors: newSeq[array[4, uint8]](1 shl bits) + ) + + if readMeta and decoder.bitReader.readBits(1) == 1: + huffmanBits = decoder.bitReader.readBits(3).int + 2 + huffmanXSize = subsampleSize(xsize, huffmanBits) + huffmanYSize = subsampleSize(ysize, huffmanBits) + var metaData = newSeq[uint8](huffmanXSize * huffmanYSize * 4) + decoder.decodeImageStream(huffmanXSize, huffmanYSize, false, metaData) + + entropyImage.setLen(huffmanXSize * huffmanYSize) + for i in 0 ..< entropyImage.len: + let metaHuffCode = + (metaData[i * 4].uint16 shl 8) or metaData[i * 4 + 1].uint16 + entropyImage[i] = metaHuffCode + numHuffGroups = max(numHuffGroups, metaHuffCode.int + 1) + + let huffmanInfo = HuffmanInfo( + xsize: huffmanXSize, + colorCache: colorCache, + hasColorCache: hasColorCache, + image: entropyImage, + bits: huffmanBits, + mask: if huffmanBits == 0: int.high else: (1 shl huffmanBits) - 1 + ) + huffmanInfo.groups.setLen(numHuffGroups) + + for groupIndex in 0 ..< numHuffGroups: + for j in 0 ..< HuffmanCodesPerMetaCode: + var size = AlphabetSize[j] + if j == 0 and hasColorCache: + size += (1 shl colorCache.bits).uint16 + huffmanInfo.groups[groupIndex][j] = decoder.readHuffmanCode(size) + + decoder.decodeImageData(xsize, ysize, huffmanInfo, data) + +proc readTransforms(decoder: LosslessDecoder): int = + result = decoder.width + + while decoder.bitReader.readBits(1) == 1: + let transformType = decoder.bitReader.readBits(2).int + if decoder.hasTransform[transformType]: + failInvalid("duplicate VP8L transform") + decoder.transformOrder.add(transformType) + + case transformType + of 0: + let + sizeBits = decoder.bitReader.readBits(3).int + 2 + blockXSize = subsampleSize(result, sizeBits) + blockYSize = subsampleSize(decoder.height, sizeBits) + var predictorData = newSeq[uint8](blockXSize * blockYSize * 4) + decoder.decodeImageStream(blockXSize, blockYSize, false, predictorData) + decoder.transforms[transformType] = LosslessTransform( + kind: PredictorTransform, + sizeBits: sizeBits, + data: predictorData + ) + of 1: + let + sizeBits = decoder.bitReader.readBits(3).int + 2 + blockXSize = subsampleSize(result, sizeBits) + blockYSize = subsampleSize(decoder.height, sizeBits) + var transformData = newSeq[uint8](blockXSize * blockYSize * 4) + decoder.decodeImageStream(blockXSize, blockYSize, false, transformData) + decoder.transforms[transformType] = LosslessTransform( + kind: ColorTransform, + sizeBits: sizeBits, + data: transformData + ) + of 2: + decoder.transforms[transformType] = LosslessTransform( + kind: SubtractGreenTransform + ) + of 3: + let colorTableSize = decoder.bitReader.readBits(8).int + 1 + var colorMap = newSeq[uint8](colorTableSize * 4) + decoder.decodeImageStream(colorTableSize, 1, false, colorMap) + for i in 4 ..< colorMap.len: + colorMap[i] = wrapByte(colorMap[i].int + colorMap[i - 4].int) + + let widthBits = + if colorTableSize <= 2: 3 + elif colorTableSize <= 4: 2 + elif colorTableSize <= 16: 1 + else: 0 + result = subsampleSize(result, widthBits) + decoder.transforms[transformType] = LosslessTransform( + kind: ColorIndexingTransform, + tableSize: colorTableSize, + data: colorMap + ) + else: + failInvalid("invalid VP8L transform") + + decoder.hasTransform[transformType] = true + +proc avg2(a, b: uint8): uint8 {.inline.} = + ((a.uint16 + b.uint16) div 2).uint8 + +proc clampByte(value: int): uint8 {.inline.} = + min(max(value, 0), 255).uint8 + +proc pixelAt(data: seq[uint8], width, x, y: int): array[4, uint8] {.inline.} = + let offset = (y * width + x) * 4 + [data[offset + 0], data[offset + 1], data[offset + 2], data[offset + 3]] + +proc addPredictor(data: var seq[uint8], offset: int, pred: array[4, uint8]) = + data[offset + 0] = wrapByte(data[offset + 0].int + pred[0].int) + data[offset + 1] = wrapByte(data[offset + 1].int + pred[1].int) + data[offset + 2] = wrapByte(data[offset + 2].int + pred[2].int) + data[offset + 3] = wrapByte(data[offset + 3].int + pred[3].int) + +proc predictor( + data: seq[uint8], width, height, x, y, mode: int +): array[4, uint8] = + if x == 0 and y == 0: + return [0'u8, 0, 0, 255] + if y == 0: + return data.pixelAt(width, x - 1, y) + if x == 0: + return data.pixelAt(width, x, y - 1) + + let + left = data.pixelAt(width, x - 1, y) + top = data.pixelAt(width, x, y - 1) + topLeft = data.pixelAt(width, x - 1, y - 1) + topRight = + if x == width - 1: data.pixelAt(width, 0, y) + else: data.pixelAt(width, x + 1, y - 1) + + case mode + of 0: + [0'u8, 0, 0, 255] + of 1: + left + of 2: + top + of 3: + topRight + of 4: + topLeft + of 5: + [ + avg2(avg2(left[0], topRight[0]), top[0]), + avg2(avg2(left[1], topRight[1]), top[1]), + avg2(avg2(left[2], topRight[2]), top[2]), + avg2(avg2(left[3], topRight[3]), top[3]) + ] + of 6: + [avg2(left[0], topLeft[0]), avg2(left[1], topLeft[1]), + avg2(left[2], topLeft[2]), avg2(left[3], topLeft[3])] + of 7: + [avg2(left[0], top[0]), avg2(left[1], top[1]), + avg2(left[2], top[2]), avg2(left[3], top[3])] + of 8: + [avg2(topLeft[0], top[0]), avg2(topLeft[1], top[1]), + avg2(topLeft[2], top[2]), avg2(topLeft[3], top[3])] + of 9: + [avg2(top[0], topRight[0]), avg2(top[1], topRight[1]), + avg2(top[2], topRight[2]), avg2(top[3], topRight[3])] + of 10: + [ + avg2(avg2(left[0], topLeft[0]), avg2(top[0], topRight[0])), + avg2(avg2(left[1], topLeft[1]), avg2(top[1], topRight[1])), + avg2(avg2(left[2], topLeft[2]), avg2(top[2], topRight[2])), + avg2(avg2(left[3], topLeft[3]), avg2(top[3], topRight[3])) + ] + of 11: + var + predictLeft = 0 + predictTop = 0 + for i in 0 .. 3: + let predict = left[i].int + top[i].int - topLeft[i].int + predictLeft += abs(predict - left[i].int) + predictTop += abs(predict - top[i].int) + if predictLeft < predictTop: left else: top + of 12: + [ + clampByte(left[0].int + top[0].int - topLeft[0].int), + clampByte(left[1].int + top[1].int - topLeft[1].int), + clampByte(left[2].int + top[2].int - topLeft[2].int), + clampByte(left[3].int + top[3].int - topLeft[3].int) + ] + of 13: + var outp: array[4, uint8] + for i in 0 .. 3: + let avg = (left[i].int + top[i].int) div 2 + outp[i] = clampByte(avg + (avg - topLeft[i].int) div 2) + outp + else: + [0'u8, 0, 0, 255] + +proc applyPredictorTransform( + data: var seq[uint8], width, height, sizeBits: int, predictorData: seq[uint8] +) = + let blockXSize = subsampleSize(width, sizeBits) + for y in 0 ..< height: + for x in 0 ..< width: + let + blockIndex = (y shr sizeBits) * blockXSize + (x shr sizeBits) + mode = + if x == 0 or y == 0: 0 + else: predictorData[blockIndex * 4 + 1].int + offset = (y * width + x) * 4 + pred = predictor(data, width, height, x, y, mode) + data.addPredictor(offset, pred) + +proc signedByte(value: uint8): int {.inline.} = + if value < 128: value.int else: value.int - 256 + +proc colorTransformDelta(t, c: uint8): int {.inline.} = + (signedByte(t) * signedByte(c)) shr 5 + +proc applyColorTransform( + data: var seq[uint8], width, height, sizeBits: int, transformData: seq[uint8] +) = + let blockXSize = subsampleSize(width, sizeBits) + for y in 0 ..< height: + for x in 0 ..< width: + let + transformOffset = ((y shr sizeBits) * blockXSize + (x shr sizeBits)) * 4 + redToBlue = transformData[transformOffset + 0] + greenToBlue = transformData[transformOffset + 1] + greenToRed = transformData[transformOffset + 2] + offset = (y * width + x) * 4 + green = data[offset + 1] + var + red = data[offset + 0].int + blue = data[offset + 2].int + red += colorTransformDelta(greenToRed, green) + blue += colorTransformDelta(greenToBlue, green) + blue += colorTransformDelta(redToBlue, wrapByte(red)) + data[offset + 0] = wrapByte(red) + data[offset + 2] = wrapByte(blue) + +proc applySubtractGreenTransform(data: var seq[uint8], len: int) = + for offset in countup(0, len - 1, 4): + data[offset + 0] = wrapByte(data[offset + 0].int + data[offset + 1].int) + data[offset + 2] = wrapByte(data[offset + 2].int + data[offset + 1].int) + +proc applyColorIndexingTransform( + data: var seq[uint8], width, height, tableSize: int, tableData: seq[uint8] +) = + if tableSize <= 0: + failInvalid("invalid VP8L color table") + + let widthBits = + if tableSize <= 2: 3 + elif tableSize <= 4: 2 + elif tableSize <= 16: 1 + else: 0 + let + packedWidth = subsampleSize(width, widthBits) + pixelsPerPacked = 1 shl widthBits + bitsPerEntry = 8 div pixelsPerPacked + mask = (1 shl bitsPerEntry) - 1 + + for y in countdown(height - 1, 0): + for x in countdown(width - 1, 0): + let + packedX = x shr widthBits + packedOffset = (y * packedWidth + packedX) * 4 + shift = (x and (pixelsPerPacked - 1)) * bitsPerEntry + index = (data[packedOffset + 1].int shr shift) and mask + dst = (y * width + x) * 4 + if index < tableSize: + let src = index * 4 + data[dst + 0] = tableData[src + 0] + data[dst + 1] = tableData[src + 1] + data[dst + 2] = tableData[src + 2] + data[dst + 3] = tableData[src + 3] + else: + data[dst + 0] = 0 + data[dst + 1] = 0 + data[dst + 2] = 0 + data[dst + 3] = 0 + +proc decodeLosslessData( + data: string, offset, size, width, height: int, implicitDimensions: bool +): seq[uint8] = + let decoder = LosslessDecoder( + bitReader: newBitReader(data, offset, size), + width: width, + height: height + ) + + if not implicitDimensions: + let signature = decoder.bitReader.readBits(8) + if signature != 0x2f: + failInvalid("invalid VP8L signature") + decoder.width = decoder.bitReader.readBits(14).int + 1 + decoder.height = decoder.bitReader.readBits(14).int + 1 + if decoder.width != width or decoder.height != height: + failInvalid("inconsistent VP8L dimensions") + discard decoder.bitReader.readBits(1) # alpha_is_used + let version = decoder.bitReader.readBits(3) + if version != 0: + failInvalid("invalid VP8L version") + + let + transformedWidth = decoder.readTransforms() + finalSize = decoder.width * decoder.height * 4 + transformedSize = transformedWidth * decoder.height * 4 + result = newSeq[uint8](finalSize) + decoder.decodeImageStream( + transformedWidth, decoder.height, true, result + ) + result.setLen(transformedSize) + + var + imageSize = transformedSize + currentWidth = transformedWidth + if decoder.transformOrder.len > 0: + for i in countdown(decoder.transformOrder.high, 0): + let transform = decoder.transforms[decoder.transformOrder[i]] + case transform.kind + of PredictorTransform: + applyPredictorTransform( + result, currentWidth, decoder.height, transform.sizeBits, transform.data + ) + of ColorTransform: + applyColorTransform( + result, currentWidth, decoder.height, transform.sizeBits, transform.data + ) + of SubtractGreenTransform: + applySubtractGreenTransform(result, imageSize) + of ColorIndexingTransform: + currentWidth = decoder.width + imageSize = finalSize + result.setLen(finalSize) + applyColorIndexingTransform( + result, currentWidth, decoder.height, transform.tableSize, transform.data + ) + + result.setLen(finalSize) + +proc newImageFromRgbaBytes(data: seq[uint8], width, height: int): Image = + result = newImage(width, height) + for i in 0 ..< width * height: + result.data[i] = rgba( + data[i * 4 + 0], + data[i * 4 + 1], + data[i * 4 + 2], + data[i * 4 + 3] + ).rgbx() + +proc newVp8BoolDecoder(data: string, offset, size: int): Vp8BoolDecoder = + data.checkBounds(offset, size) + Vp8BoolDecoder( + data: data, + pos: offset, + endPos: offset + size, + range: 255, + bitCount: -8 + ) + +proc loadByte(decoder: Vp8BoolDecoder) = + if decoder.pos < decoder.endPos: + decoder.value = (decoder.value shl 8) or + decoder.data.readUint8(decoder.pos).uint64 + inc decoder.pos + decoder.bitCount += 8 + elif not decoder.zeroByteAfterEof: + decoder.value = decoder.value shl 8 + decoder.bitCount += 8 + decoder.zeroByteAfterEof = true + else: + failInvalid("truncated VP8 bitstream") + +proc readBool(decoder: Vp8BoolDecoder, probability: uint8): bool = + if decoder.bitCount < 0: + decoder.loadByte() + + let + split = 1'u32 + (((decoder.range - 1) * probability.uint32) shr 8) + bigSplit = split.uint64 shl decoder.bitCount + + if decoder.value >= bigSplit: + decoder.range -= split + decoder.value -= bigSplit + result = true + else: + decoder.range = split + + if decoder.range == 0: + failInvalid("corrupt VP8 bitstream") + + while decoder.range < 128: + decoder.range = decoder.range shl 1 + dec decoder.bitCount + +proc readFlag(decoder: Vp8BoolDecoder): bool {.inline.} = + decoder.readBool(128) + +proc readLiteral(decoder: Vp8BoolDecoder, bits: int): uint8 = + var value: uint8 + for _ in 0 ..< bits: + value = (value shl 1) or decoder.readFlag().uint8 + return value + +proc readOptionalSignedValue(decoder: Vp8BoolDecoder, bits: int): int = + if not decoder.readFlag(): + return 0 + let value = decoder.readLiteral(bits).int + if decoder.readFlag(): + return -value + return value + +proc readTree( + decoder: Vp8BoolDecoder, + tree: openArray[int], + probabilities: openArray[uint8], + start = 0 +): int = + var index = start + while true: + if index < 0 or index >= probabilities.len or index * 2 + 1 >= tree.len: + failInvalid("invalid VP8 probability tree") + let branch = + if decoder.readBool(probabilities[index]): tree[index * 2 + 1] + else: tree[index * 2] + if branch <= 0: + return -branch + index = branch div 2 + +proc vp8ClampByte(value: int): uint8 {.inline.} = + if value <= 0: + 0 + elif value >= 255: + 255 + else: + value.uint8 + +proc idct4x4(coeffs: var array[16, int]) = + const + Const1 = 20091 + Const2 = 35468 + + for i in 0 ..< 4: + let + a1 = coeffs[i] + coeffs[8 + i] + b1 = coeffs[i] - coeffs[8 + i] + t1 = (coeffs[4 + i] * Const2) shr 16 + t2 = coeffs[12 + i] + ((coeffs[12 + i] * Const1) shr 16) + c1 = t1 - t2 + t3 = coeffs[4 + i] + ((coeffs[4 + i] * Const1) shr 16) + t4 = (coeffs[12 + i] * Const2) shr 16 + d1 = t3 + t4 + coeffs[i] = a1 + d1 + coeffs[4 + i] = b1 + c1 + coeffs[12 + i] = a1 - d1 + coeffs[8 + i] = b1 - c1 + + for i in 0 ..< 4: + let + a1 = coeffs[4 * i] + coeffs[4 * i + 2] + b1 = coeffs[4 * i] - coeffs[4 * i + 2] + t1 = (coeffs[4 * i + 1] * Const2) shr 16 + t2 = coeffs[4 * i + 3] + ((coeffs[4 * i + 3] * Const1) shr 16) + c1 = t1 - t2 + t3 = coeffs[4 * i + 1] + ((coeffs[4 * i + 1] * Const1) shr 16) + t4 = (coeffs[4 * i + 3] * Const2) shr 16 + d1 = t3 + t4 + coeffs[4 * i] = (a1 + d1 + 4) shr 3 + coeffs[4 * i + 3] = (a1 - d1 + 4) shr 3 + coeffs[4 * i + 1] = (b1 + c1 + 4) shr 3 + coeffs[4 * i + 2] = (b1 - c1 + 4) shr 3 + +proc iwht4x4(coeffs: var array[16, int]) = + for i in 0 ..< 4: + let + a1 = coeffs[i] + coeffs[12 + i] + b1 = coeffs[4 + i] + coeffs[8 + i] + c1 = coeffs[4 + i] - coeffs[8 + i] + d1 = coeffs[i] - coeffs[12 + i] + coeffs[i] = a1 + b1 + coeffs[4 + i] = c1 + d1 + coeffs[8 + i] = a1 - b1 + coeffs[12 + i] = d1 - c1 + + for y in 0 ..< 4: + let + pos = y * 4 + a1 = coeffs[pos] + coeffs[pos + 3] + b1 = coeffs[pos + 1] + coeffs[pos + 2] + c1 = coeffs[pos + 1] - coeffs[pos + 2] + d1 = coeffs[pos] - coeffs[pos + 3] + a2 = a1 + b1 + b2 = c1 + d1 + c2 = a1 - b1 + d2 = d1 - c1 + coeffs[pos] = (a2 + 3) shr 3 + coeffs[pos + 1] = (b2 + 3) shr 3 + coeffs[pos + 2] = (c2 + 3) shr 3 + coeffs[pos + 3] = (d2 + 3) shr 3 + +const + Vp8LumaStride = 21 + Vp8LumaBlockSize = 21 * 17 + Vp8ChromaStride = 9 + Vp8ChromaBlockSize = 9 * 9 + +proc createBorderLuma( + mbx, mby, mbWidth: int, top, left: seq[uint8] +): array[Vp8LumaBlockSize, uint8] = + if mby == 0: + for x in 1 ..< Vp8LumaStride: + result[x] = 127 + else: + for x in 0 ..< 16: + result[1 + x] = top[mbx * 16 + x] + for x in 0 ..< 4: + result[17 + x] = + if mbx == mbWidth - 1: top[mbx * 16 + 15] + else: top[mbx * 16 + 16 + x] + + for i in 17 ..< Vp8LumaStride: + result[4 * Vp8LumaStride + i] = result[i] + result[8 * Vp8LumaStride + i] = result[i] + result[12 * Vp8LumaStride + i] = result[i] + + if mbx == 0: + for y in 0 ..< 16: + result[(y + 1) * Vp8LumaStride] = 129 + else: + for y in 0 ..< 16: + result[(y + 1) * Vp8LumaStride] = left[1 + y] + + result[0] = + if mby == 0: 127 + elif mbx == 0: 129 + else: left[0] + +proc createBorderChroma( + mbx, mby: int, top, left: seq[uint8] +): array[Vp8ChromaBlockSize, uint8] = + if mby == 0: + for x in 1 ..< Vp8ChromaStride: + result[x] = 127 + else: + for x in 0 ..< 8: + result[1 + x] = top[mbx * 8 + x] + + if mbx == 0: + for y in 0 ..< 8: + result[(y + 1) * Vp8ChromaStride] = 129 + else: + for y in 0 ..< 8: + result[(y + 1) * Vp8ChromaStride] = left[1 + y] + + result[0] = + if mby == 0: 127 + elif mbx == 0: 129 + else: left[0] + +proc addResidue( + pixels: var openArray[uint8], + residue: openArray[int], + blockStart, y0, x0, stride: int +) = + var + pos = y0 * stride + x0 + src = blockStart + for _ in 0 ..< 4: + for x in 0 ..< 4: + pixels[pos + x] = vp8ClampByte(pixels[pos + x].int + residue[src + x]) + pos += stride + src += 4 + +proc vp8Avg3(left, center, right: uint8): uint8 {.inline.} = + ((left.uint16 + 2'u16 * center.uint16 + right.uint16 + 2) shr 2).uint8 + +proc vp8Avg2(center, right: uint8): uint8 {.inline.} = + ((center.uint16 + right.uint16 + 1) shr 1).uint8 + +proc predictVpred( + pixels: var openArray[uint8], size, x0, y0, stride: int +) = + for y in 0 ..< size: + for x in 0 ..< size: + pixels[(y0 + y) * stride + x0 + x] = + pixels[(y0 - 1) * stride + x0 + x] + +proc predictHpred( + pixels: var openArray[uint8], size, x0, y0, stride: int +) = + for y in 0 ..< size: + let left = pixels[(y0 + y) * stride + x0 - 1] + for x in 0 ..< size: + pixels[(y0 + y) * stride + x0 + x] = left + +proc predictDcpred( + pixels: var openArray[uint8], size, stride: int, above, left: bool +) = + var + sum = 0'u32 + shift = if size == 8: 2 else: 3 + + if left: + for y in 0 ..< size: + sum += pixels[(y + 1) * stride].uint32 + inc shift + + if above: + for x in 1 .. size: + sum += pixels[x].uint32 + inc shift + + let dcValue = + if not left and not above: 128.uint8 + else: ((sum + (1'u32 shl (shift - 1))) shr shift).uint8 + + for y in 0 ..< size: + for x in 0 ..< size: + pixels[(y + 1) * stride + 1 + x] = dcValue + +proc predictTmpred( + pixels: var openArray[uint8], size, x0, y0, stride: int +) = + let topLeft = pixels[(y0 - 1) * stride + x0 - 1].int + for y in 0 ..< size: + let leftMinusTopLeft = pixels[(y0 + y) * stride + x0 - 1].int - topLeft + for x in 0 ..< size: + pixels[(y0 + y) * stride + x0 + x] = vp8ClampByte( + leftMinusTopLeft + pixels[(y0 - 1) * stride + x0 + x].int + ) + +proc predictBDcpred( + pixels: var openArray[uint8], x0, y0, stride: int +) = + var value = 4'u32 + for x in 0 ..< 4: + value += pixels[(y0 - 1) * stride + x0 + x].uint32 + for y in 0 ..< 4: + value += pixels[(y0 + y) * stride + x0 - 1].uint32 + value = value shr 3 + for y in 0 ..< 4: + for x in 0 ..< 4: + pixels[(y0 + y) * stride + x0 + x] = value.uint8 + +proc topPixel( + pixels: openArray[uint8], x0, y0, stride, offset: int +): uint8 {.inline.} = + pixels[(y0 - 1) * stride + x0 + offset] + +proc leftPixel( + pixels: openArray[uint8], x0, y0, stride, offset: int +): uint8 {.inline.} = + pixels[(y0 + offset) * stride + x0 - 1] + +proc edgePixel( + pixels: openArray[uint8], x0, y0, stride, index: int +): uint8 = + let pos = (y0 - 1) * stride + x0 - 1 + case index + of 0: pixels[pos + 4 * stride] + of 1: pixels[pos + 3 * stride] + of 2: pixels[pos + 2 * stride] + of 3: pixels[pos + stride] + else: pixels[pos + index - 4] + +proc predictBVePred( + pixels: var openArray[uint8], x0, y0, stride: int +) = + let + p = pixels[(y0 - 1) * stride + x0 - 1] + a0 = topPixel(pixels, x0, y0, stride, 0) + a1 = topPixel(pixels, x0, y0, stride, 1) + a2 = topPixel(pixels, x0, y0, stride, 2) + a3 = topPixel(pixels, x0, y0, stride, 3) + a4 = topPixel(pixels, x0, y0, stride, 4) + values = [ + vp8Avg3(p, a0, a1), vp8Avg3(a0, a1, a2), + vp8Avg3(a1, a2, a3), vp8Avg3(a2, a3, a4) + ] + for y in 0 ..< 4: + for x in 0 ..< 4: + pixels[(y0 + y) * stride + x0 + x] = values[x] + +proc predictBHePred( + pixels: var openArray[uint8], x0, y0, stride: int +) = + let + p = pixels[(y0 - 1) * stride + x0 - 1] + l0 = leftPixel(pixels, x0, y0, stride, 0) + l1 = leftPixel(pixels, x0, y0, stride, 1) + l2 = leftPixel(pixels, x0, y0, stride, 2) + l3 = leftPixel(pixels, x0, y0, stride, 3) + values = [ + vp8Avg3(p, l0, l1), vp8Avg3(l0, l1, l2), + vp8Avg3(l1, l2, l3), vp8Avg3(l2, l3, l3) + ] + for y in 0 ..< 4: + for x in 0 ..< 4: + pixels[(y0 + y) * stride + x0 + x] = values[y] + +proc predictBLdPred( + pixels: var openArray[uint8], x0, y0, stride: int +) = + var a: array[8, uint8] + for i in 0 ..< 8: + a[i] = topPixel(pixels, x0, y0, stride, i) + let values = [ + vp8Avg3(a[0], a[1], a[2]), vp8Avg3(a[1], a[2], a[3]), + vp8Avg3(a[2], a[3], a[4]), vp8Avg3(a[3], a[4], a[5]), + vp8Avg3(a[4], a[5], a[6]), vp8Avg3(a[5], a[6], a[7]), + vp8Avg3(a[6], a[7], a[7]) + ] + for y in 0 ..< 4: + for x in 0 ..< 4: + pixels[(y0 + y) * stride + x0 + x] = values[y + x] + +proc predictBRdPred( + pixels: var openArray[uint8], x0, y0, stride: int +) = + var e: array[9, uint8] + for i in 0 ..< 9: + e[i] = edgePixel(pixels, x0, y0, stride, i) + let values = [ + vp8Avg3(e[0], e[1], e[2]), vp8Avg3(e[1], e[2], e[3]), + vp8Avg3(e[2], e[3], e[4]), vp8Avg3(e[3], e[4], e[5]), + vp8Avg3(e[4], e[5], e[6]), vp8Avg3(e[5], e[6], e[7]), + vp8Avg3(e[6], e[7], e[8]) + ] + for y in 0 ..< 4: + for x in 0 ..< 4: + pixels[(y0 + y) * stride + x0 + x] = values[3 - y + x] + +proc predictBVrPred( + pixels: var openArray[uint8], x0, y0, stride: int +) = + var e: array[9, uint8] + for i in 0 ..< 9: + e[i] = edgePixel(pixels, x0, y0, stride, i) + pixels[(y0 + 3) * stride + x0] = vp8Avg3(e[1], e[2], e[3]) + pixels[(y0 + 2) * stride + x0] = vp8Avg3(e[2], e[3], e[4]) + pixels[(y0 + 3) * stride + x0 + 1] = vp8Avg3(e[3], e[4], e[5]) + pixels[(y0 + 1) * stride + x0] = vp8Avg3(e[3], e[4], e[5]) + pixels[(y0 + 2) * stride + x0 + 1] = vp8Avg2(e[4], e[5]) + pixels[y0 * stride + x0] = vp8Avg2(e[4], e[5]) + pixels[(y0 + 3) * stride + x0 + 2] = vp8Avg3(e[4], e[5], e[6]) + pixels[(y0 + 1) * stride + x0 + 1] = vp8Avg3(e[4], e[5], e[6]) + pixels[(y0 + 2) * stride + x0 + 2] = vp8Avg2(e[5], e[6]) + pixels[y0 * stride + x0 + 1] = vp8Avg2(e[5], e[6]) + pixels[(y0 + 3) * stride + x0 + 3] = vp8Avg3(e[5], e[6], e[7]) + pixels[(y0 + 1) * stride + x0 + 2] = vp8Avg3(e[5], e[6], e[7]) + pixels[(y0 + 2) * stride + x0 + 3] = vp8Avg2(e[6], e[7]) + pixels[y0 * stride + x0 + 2] = vp8Avg2(e[6], e[7]) + pixels[(y0 + 1) * stride + x0 + 3] = vp8Avg3(e[6], e[7], e[8]) + pixels[y0 * stride + x0 + 3] = vp8Avg2(e[7], e[8]) + +proc predictBVlPred( + pixels: var openArray[uint8], x0, y0, stride: int +) = + var a: array[8, uint8] + for i in 0 ..< 8: + a[i] = topPixel(pixels, x0, y0, stride, i) + pixels[y0 * stride + x0] = vp8Avg2(a[0], a[1]) + pixels[(y0 + 1) * stride + x0] = vp8Avg3(a[0], a[1], a[2]) + pixels[(y0 + 2) * stride + x0] = vp8Avg2(a[1], a[2]) + pixels[y0 * stride + x0 + 1] = vp8Avg2(a[1], a[2]) + pixels[(y0 + 1) * stride + x0 + 1] = vp8Avg3(a[1], a[2], a[3]) + pixels[(y0 + 3) * stride + x0] = vp8Avg3(a[1], a[2], a[3]) + pixels[(y0 + 2) * stride + x0 + 1] = vp8Avg2(a[2], a[3]) + pixels[y0 * stride + x0 + 2] = vp8Avg2(a[2], a[3]) + pixels[(y0 + 3) * stride + x0 + 1] = vp8Avg3(a[2], a[3], a[4]) + pixels[(y0 + 1) * stride + x0 + 2] = vp8Avg3(a[2], a[3], a[4]) + pixels[(y0 + 2) * stride + x0 + 2] = vp8Avg2(a[3], a[4]) + pixels[y0 * stride + x0 + 3] = vp8Avg2(a[3], a[4]) + pixels[(y0 + 3) * stride + x0 + 2] = vp8Avg3(a[3], a[4], a[5]) + pixels[(y0 + 1) * stride + x0 + 3] = vp8Avg3(a[3], a[4], a[5]) + pixels[(y0 + 2) * stride + x0 + 3] = vp8Avg3(a[4], a[5], a[6]) + pixels[(y0 + 3) * stride + x0 + 3] = vp8Avg3(a[5], a[6], a[7]) + +proc predictBHdPred( + pixels: var openArray[uint8], x0, y0, stride: int +) = + var e: array[9, uint8] + for i in 0 ..< 9: + e[i] = edgePixel(pixels, x0, y0, stride, i) + pixels[(y0 + 3) * stride + x0] = vp8Avg2(e[0], e[1]) + pixels[(y0 + 3) * stride + x0 + 1] = vp8Avg3(e[0], e[1], e[2]) + pixels[(y0 + 2) * stride + x0] = vp8Avg2(e[1], e[2]) + pixels[(y0 + 3) * stride + x0 + 2] = vp8Avg2(e[1], e[2]) + pixels[(y0 + 2) * stride + x0 + 1] = vp8Avg3(e[1], e[2], e[3]) + pixels[(y0 + 3) * stride + x0 + 3] = vp8Avg3(e[1], e[2], e[3]) + pixels[(y0 + 2) * stride + x0 + 2] = vp8Avg2(e[2], e[3]) + pixels[(y0 + 1) * stride + x0] = vp8Avg2(e[2], e[3]) + pixels[(y0 + 2) * stride + x0 + 3] = vp8Avg3(e[2], e[3], e[4]) + pixels[(y0 + 1) * stride + x0 + 1] = vp8Avg3(e[2], e[3], e[4]) + pixels[(y0 + 1) * stride + x0 + 2] = vp8Avg2(e[3], e[4]) + pixels[y0 * stride + x0] = vp8Avg2(e[3], e[4]) + pixels[(y0 + 1) * stride + x0 + 3] = vp8Avg3(e[3], e[4], e[5]) + pixels[y0 * stride + x0 + 1] = vp8Avg3(e[3], e[4], e[5]) + pixels[y0 * stride + x0 + 2] = vp8Avg3(e[4], e[5], e[6]) + pixels[y0 * stride + x0 + 3] = vp8Avg3(e[5], e[6], e[7]) + +proc predictBHuPred( + pixels: var openArray[uint8], x0, y0, stride: int +) = + let + l0 = leftPixel(pixels, x0, y0, stride, 0) + l1 = leftPixel(pixels, x0, y0, stride, 1) + l2 = leftPixel(pixels, x0, y0, stride, 2) + l3 = leftPixel(pixels, x0, y0, stride, 3) + pixels[y0 * stride + x0] = vp8Avg2(l0, l1) + pixels[y0 * stride + x0 + 1] = vp8Avg3(l0, l1, l2) + pixels[y0 * stride + x0 + 2] = vp8Avg2(l1, l2) + pixels[(y0 + 1) * stride + x0] = vp8Avg2(l1, l2) + pixels[y0 * stride + x0 + 3] = vp8Avg3(l1, l2, l3) + pixels[(y0 + 1) * stride + x0 + 1] = vp8Avg3(l1, l2, l3) + pixels[(y0 + 1) * stride + x0 + 2] = vp8Avg2(l2, l3) + pixels[(y0 + 2) * stride + x0] = vp8Avg2(l2, l3) + pixels[(y0 + 1) * stride + x0 + 3] = vp8Avg3(l2, l3, l3) + pixels[(y0 + 2) * stride + x0 + 1] = vp8Avg3(l2, l3, l3) + for y in 2 ..< 4: + for x in 0 ..< 4: + if y == 2 and x < 2: + continue + pixels[(y0 + y) * stride + x0 + x] = l3 + +proc predict4x4( + pixels: var openArray[uint8], + stride: int, + modes: array[16, int], + residue: openArray[int] +) = + for sby in 0 ..< 4: + for sbx in 0 ..< 4: + let + i = sbx + sby * 4 + y0 = sby * 4 + 1 + x0 = sbx * 4 + 1 + case modes[i] + of Vp8BTmPred: predictTmpred(pixels, 4, x0, y0, stride) + of Vp8BVePred: predictBVePred(pixels, x0, y0, stride) + of Vp8BHePred: predictBHePred(pixels, x0, y0, stride) + of Vp8BDcPred: predictBDcpred(pixels, x0, y0, stride) + of Vp8BLdPred: predictBLdPred(pixels, x0, y0, stride) + of Vp8BRdPred: predictBRdPred(pixels, x0, y0, stride) + of Vp8BVrPred: predictBVrPred(pixels, x0, y0, stride) + of Vp8BVlPred: predictBVlPred(pixels, x0, y0, stride) + of Vp8BHdPred: predictBHdPred(pixels, x0, y0, stride) + of Vp8BHuPred: predictBHuPred(pixels, x0, y0, stride) + else: failInvalid("invalid VP8 intra prediction mode") + pixels.addResidue(residue, i * 16, y0, x0, stride) + +proc signedClamp(value: int): int {.inline.} = + max(-128, min(127, value)) + +proc unsignedToSigned(value: uint8): int {.inline.} = + value.int - 128 + +proc signedToUnsigned(value: int): uint8 {.inline.} = + (signedClamp(value) + 128).uint8 + +proc byteDiff(a, b: uint8): uint8 {.inline.} = + abs(a.int - b.int).uint8 + +proc commonAdjustVertical( + pixels: var seq[uint8], point, stride: int, useOuterTaps: bool +): int = + let + p1 = unsignedToSigned(pixels[point - 2 * stride]) + p0 = unsignedToSigned(pixels[point - stride]) + q0 = unsignedToSigned(pixels[point]) + q1 = unsignedToSigned(pixels[point + stride]) + outer = if useOuterTaps: signedClamp(p1 - q1) else: 0 + a0 = signedClamp(outer + 3 * (q0 - p0)) + b = signedClamp(a0 + 3) shr 3 + a = signedClamp(a0 + 4) shr 3 + pixels[point] = signedToUnsigned(q0 - a) + pixels[point - stride] = signedToUnsigned(p0 + b) + return a + +proc commonAdjustHorizontal( + pixels: var seq[uint8], start: int, useOuterTaps: bool +): int = + let + p1 = unsignedToSigned(pixels[start + 2]) + p0 = unsignedToSigned(pixels[start + 3]) + q0 = unsignedToSigned(pixels[start + 4]) + q1 = unsignedToSigned(pixels[start + 5]) + outer = if useOuterTaps: signedClamp(p1 - q1) else: 0 + a0 = signedClamp(outer + 3 * (q0 - p0)) + b = signedClamp(a0 + 3) shr 3 + a = signedClamp(a0 + 4) shr 3 + pixels[start + 4] = signedToUnsigned(q0 - a) + pixels[start + 3] = signedToUnsigned(p0 + b) + return a + +proc simpleThresholdVertical( + edgeLimit: int, pixels: seq[uint8], point, stride: int +): bool = + byteDiff(pixels[point - stride], pixels[point]).int * 2 + + byteDiff(pixels[point - 2 * stride], pixels[point + stride]).int div 2 <= + edgeLimit + +proc simpleThresholdHorizontal( + edgeLimit: int, pixels: seq[uint8], start: int +): bool = + byteDiff(pixels[start + 3], pixels[start + 4]).int * 2 + + byteDiff(pixels[start + 2], pixels[start + 5]).int div 2 <= edgeLimit + +proc shouldFilterVertical( + interiorLimit, edgeLimit: uint8, pixels: seq[uint8], point, stride: int +): bool = + simpleThresholdVertical(edgeLimit.int, pixels, point, stride) and + byteDiff(pixels[point - 4 * stride], pixels[point - 3 * stride]) <= + interiorLimit and + byteDiff(pixels[point - 3 * stride], pixels[point - 2 * stride]) <= + interiorLimit and + byteDiff(pixels[point - 2 * stride], pixels[point - stride]) <= + interiorLimit and + byteDiff(pixels[point + 3 * stride], pixels[point + 2 * stride]) <= + interiorLimit and + byteDiff(pixels[point + 2 * stride], pixels[point + stride]) <= + interiorLimit and + byteDiff(pixels[point + stride], pixels[point]) <= interiorLimit + +proc shouldFilterHorizontal( + interiorLimit, edgeLimit: uint8, pixels: seq[uint8], start: int +): bool = + simpleThresholdHorizontal(edgeLimit.int, pixels, start) and + byteDiff(pixels[start], pixels[start + 1]) <= interiorLimit and + byteDiff(pixels[start + 1], pixels[start + 2]) <= interiorLimit and + byteDiff(pixels[start + 2], pixels[start + 3]) <= interiorLimit and + byteDiff(pixels[start + 7], pixels[start + 6]) <= interiorLimit and + byteDiff(pixels[start + 6], pixels[start + 5]) <= interiorLimit and + byteDiff(pixels[start + 5], pixels[start + 4]) <= interiorLimit + +proc highEdgeVarianceVertical( + threshold: uint8, pixels: seq[uint8], point, stride: int +): bool = + byteDiff(pixels[point - 2 * stride], pixels[point - stride]) > threshold or + byteDiff(pixels[point + stride], pixels[point]) > threshold + +proc highEdgeVarianceHorizontal( + threshold: uint8, pixels: seq[uint8], start: int +): bool = + byteDiff(pixels[start + 2], pixels[start + 3]) > threshold or + byteDiff(pixels[start + 5], pixels[start + 4]) > threshold + +proc simpleSegmentVertical( + pixels: var seq[uint8], edgeLimit: uint8, point, stride: int +) = + if simpleThresholdVertical(edgeLimit.int, pixels, point, stride): + discard pixels.commonAdjustVertical(point, stride, true) + +proc simpleSegmentHorizontal( + pixels: var seq[uint8], edgeLimit: uint8, start: int +) = + if simpleThresholdHorizontal(edgeLimit.int, pixels, start): + discard pixels.commonAdjustHorizontal(start, true) + +proc subblockFilterVertical( + pixels: var seq[uint8], + hevThreshold, interiorLimit, edgeLimit: uint8, + point, stride: int +) = + if shouldFilterVertical(interiorLimit, edgeLimit, pixels, point, stride): + let + highVariance = highEdgeVarianceVertical(hevThreshold, pixels, point, stride) + a = (pixels.commonAdjustVertical(point, stride, highVariance) + 1) shr 1 + if not highVariance: + pixels[point + stride] = + signedToUnsigned(unsignedToSigned(pixels[point + stride]) - a) + pixels[point - 2 * stride] = + signedToUnsigned(unsignedToSigned(pixels[point - 2 * stride]) + a) + +proc subblockFilterHorizontal( + pixels: var seq[uint8], + hevThreshold, interiorLimit, edgeLimit: uint8, + start: int +) = + if shouldFilterHorizontal(interiorLimit, edgeLimit, pixels, start): + let + highVariance = highEdgeVarianceHorizontal(hevThreshold, pixels, start) + a = (pixels.commonAdjustHorizontal(start, highVariance) + 1) shr 1 + if not highVariance: + pixels[start + 5] = signedToUnsigned(unsignedToSigned(pixels[start + 5]) - a) + pixels[start + 2] = signedToUnsigned(unsignedToSigned(pixels[start + 2]) + a) + +proc macroblockFilterVertical( + pixels: var seq[uint8], + hevThreshold, interiorLimit, edgeLimit: uint8, + point, stride: int +) = + if shouldFilterVertical(interiorLimit, edgeLimit, pixels, point, stride): + if not highEdgeVarianceVertical(hevThreshold, pixels, point, stride): + let + p2 = unsignedToSigned(pixels[point - 3 * stride]) + p1 = unsignedToSigned(pixels[point - 2 * stride]) + p0 = unsignedToSigned(pixels[point - stride]) + q0 = unsignedToSigned(pixels[point]) + q1 = unsignedToSigned(pixels[point + stride]) + q2 = unsignedToSigned(pixels[point + 2 * stride]) + w = signedClamp(signedClamp(p1 - q1) + 3 * (q0 - p0)) + a0 = signedClamp((27 * w + 63) shr 7) + a1 = signedClamp((18 * w + 63) shr 7) + a2 = signedClamp((9 * w + 63) shr 7) + pixels[point] = signedToUnsigned(q0 - a0) + pixels[point - stride] = signedToUnsigned(p0 + a0) + pixels[point + stride] = signedToUnsigned(q1 - a1) + pixels[point - 2 * stride] = signedToUnsigned(p1 + a1) + pixels[point + 2 * stride] = signedToUnsigned(q2 - a2) + pixels[point - 3 * stride] = signedToUnsigned(p2 + a2) + else: + discard pixels.commonAdjustVertical(point, stride, true) + +proc macroblockFilterHorizontal( + pixels: var seq[uint8], + hevThreshold, interiorLimit, edgeLimit: uint8, + start: int +) = + if shouldFilterHorizontal(interiorLimit, edgeLimit, pixels, start): + if not highEdgeVarianceHorizontal(hevThreshold, pixels, start): + let + p2 = unsignedToSigned(pixels[start + 1]) + p1 = unsignedToSigned(pixels[start + 2]) + p0 = unsignedToSigned(pixels[start + 3]) + q0 = unsignedToSigned(pixels[start + 4]) + q1 = unsignedToSigned(pixels[start + 5]) + q2 = unsignedToSigned(pixels[start + 6]) + w = signedClamp(signedClamp(p1 - q1) + 3 * (q0 - p0)) + a0 = signedClamp((27 * w + 63) shr 7) + a1 = signedClamp((18 * w + 63) shr 7) + a2 = signedClamp((9 * w + 63) shr 7) + pixels[start + 4] = signedToUnsigned(q0 - a0) + pixels[start + 3] = signedToUnsigned(p0 + a0) + pixels[start + 5] = signedToUnsigned(q1 - a1) + pixels[start + 2] = signedToUnsigned(p1 + a1) + pixels[start + 6] = signedToUnsigned(q2 - a2) + pixels[start + 1] = signedToUnsigned(p2 + a2) + else: + discard pixels.commonAdjustHorizontal(start, true) + +proc newVp8Decoder(): Vp8Decoder = + Vp8Decoder( + frame: Vp8Frame(), + numPartitions: 1, + segmentProbs: [255.uint8, 255, 255], + tokenProbs: Vp8CoeffProbs + ) + +proc initPartitions( + decoder: Vp8Decoder, data: string, offset, endPos, n: int +) = + if n < 1 or n > 8: + failInvalid("invalid VP8 partition count") + + var pos = offset + if n > 1: + data.checkBounds(pos, 3 * (n - 1)) + var sizes = newSeq[int](n - 1) + for i in 0 ..< n - 1: + sizes[i] = data.readUint24le(pos + i * 3) + pos += 3 * (n - 1) + + for i in 0 ..< n - 1: + if sizes[i] > endPos - pos: + failInvalid("truncated VP8 partition") + decoder.partitions[i] = newVp8BoolDecoder(data, pos, sizes[i]) + pos += sizes[i] + + if pos > endPos: + failInvalid("truncated VP8 partition") + decoder.partitions[n - 1] = newVp8BoolDecoder(data, pos, endPos - pos) + +proc updateTokenProbabilities(decoder: Vp8Decoder) = + for i in 0 ..< 4: + for j in 0 ..< 8: + for k in 0 ..< 3: + for t in 0 ..< 11: + if decoder.b.readBool(Vp8CoeffUpdateProbs[i][j][k][t]): + decoder.tokenProbs[i][j][k][t] = decoder.b.readLiteral(8) + +proc clampQuantIndex(value: int): int {.inline.} = + max(0, min(127, value)) + +proc readQuantizationIndices(decoder: Vp8Decoder) = + let + yacAbs = decoder.b.readLiteral(7).int + ydcDelta = decoder.b.readOptionalSignedValue(4) + y2dcDelta = decoder.b.readOptionalSignedValue(4) + y2acDelta = decoder.b.readOptionalSignedValue(4) + uvdcDelta = decoder.b.readOptionalSignedValue(4) + uvacDelta = decoder.b.readOptionalSignedValue(4) + segmentCount = if decoder.segmentsEnabled: 4 else: 1 + + for i in 0 ..< segmentCount: + let base = + if decoder.segmentsEnabled: + if decoder.segments[i].deltaValues: + decoder.segments[i].quantizerLevel.int + yacAbs + else: + decoder.segments[i].quantizerLevel.int + else: + yacAbs + + decoder.segments[i].ydc = Vp8DcQuant[clampQuantIndex(base + ydcDelta)] + decoder.segments[i].yac = Vp8AcQuant[clampQuantIndex(base)] + decoder.segments[i].y2dc = + (Vp8DcQuant[clampQuantIndex(base + y2dcDelta)].int * 2).int16 + decoder.segments[i].y2ac = + (Vp8AcQuant[clampQuantIndex(base + y2acDelta)].int * 155 div 100).int16 + decoder.segments[i].uvdc = Vp8DcQuant[clampQuantIndex(base + uvdcDelta)] + decoder.segments[i].uvac = Vp8AcQuant[clampQuantIndex(base + uvacDelta)] + if decoder.segments[i].y2ac < 8: + decoder.segments[i].y2ac = 8 + if decoder.segments[i].uvdc > 132: + decoder.segments[i].uvdc = 132 + +proc readLoopFilterAdjustments(decoder: Vp8Decoder) = + if decoder.b.readFlag(): + for i in 0 ..< 4: + decoder.refDelta[i] = decoder.b.readOptionalSignedValue(6) + for i in 0 ..< 4: + decoder.modeDelta[i] = decoder.b.readOptionalSignedValue(6) + +proc readSegmentUpdates(decoder: Vp8Decoder) = + decoder.segmentsUpdateMap = decoder.b.readFlag() + let updateSegmentFeatureData = decoder.b.readFlag() + + if updateSegmentFeatureData: + let segmentFeatureMode = decoder.b.readFlag() + for i in 0 ..< 4: + decoder.segments[i].deltaValues = not segmentFeatureMode + for i in 0 ..< 4: + decoder.segments[i].quantizerLevel = + decoder.b.readOptionalSignedValue(7).int8 + for i in 0 ..< 4: + decoder.segments[i].loopfilterLevel = + decoder.b.readOptionalSignedValue(6).int8 + + if decoder.segmentsUpdateMap: + for i in 0 ..< 3: + decoder.segmentProbs[i] = + if decoder.b.readFlag(): decoder.b.readLiteral(8) + else: 255 + +proc lumaModeToIntra(mode: int): int = + case mode + of Vp8DcPred: Vp8BDcPred + of Vp8VPred: Vp8BVePred + of Vp8HPred: Vp8BHePred + of Vp8TmPred: Vp8BTmPred + else: -1 + +proc readMacroblockHeader( + decoder: Vp8Decoder, mbx: int +): Vp8MacroBlock = + if decoder.segmentsEnabled and decoder.segmentsUpdateMap: + result.segmentId = decoder.b.readTree( + Vp8SegmentIdTree, decoder.segmentProbs + ).uint8 + + result.coeffsSkipped = + if decoder.hasProbSkipFalse: decoder.b.readBool(decoder.probSkipFalse) + else: false + + result.lumaMode = decoder.b.readTree( + Vp8KeyframeYModeTree, Vp8KeyframeYModeProbs + ) + if result.lumaMode < Vp8DcPred or result.lumaMode > Vp8BPred: + failInvalid("invalid VP8 luma prediction mode") + + let blockMode = lumaModeToIntra(result.lumaMode) + if blockMode < 0: + for y in 0 ..< 4: + for x in 0 ..< 4: + let + top = decoder.top[mbx].bpred[x] + left = decoder.left.bpred[y] + mode = decoder.b.readTree( + Vp8KeyframeBpredModeTree, + Vp8KeyframeBpredModeProbs[top][left] + ) + if mode < Vp8BDcPred or mode > Vp8BHuPred: + failInvalid("invalid VP8 intra prediction mode") + result.bpred[x + y * 4] = mode + decoder.top[mbx].bpred[x] = mode + decoder.left.bpred[y] = mode + else: + for i in 0 ..< 4: + result.bpred[12 + i] = blockMode + decoder.left.bpred[i] = blockMode + + result.chromaMode = decoder.b.readTree( + Vp8KeyframeUvModeTree, Vp8KeyframeUvModeProbs + ) + if result.chromaMode < Vp8DcPred or result.chromaMode > Vp8TmPred: + failInvalid("invalid VP8 chroma prediction mode") + + for i in 0 ..< 4: + decoder.top[mbx].bpred[i] = result.bpred[12 + i] + +proc readCoefficients( + decoder: Vp8Decoder, + coeffBlock: var array[16, int], + partitionIndex, plane, complexity: int, + dcq, acq: int16 +): bool = + let partition = decoder.partitions[partitionIndex] + var + complexityState = complexity + skip = false + let firstCoeff = if plane == 0: 1 else: 0 + + for i in firstCoeff ..< 16: + let + band = Vp8CoeffBands[i].int + start = if skip: 1 else: 0 + token = partition.readTree( + Vp8DctTokenTree, decoder.tokenProbs[plane][band][complexityState], start + ) + + var absValue: int + case token + of Vp8DctEob: + break + of Vp8Dct0: + skip = true + result = true + complexityState = 0 + continue + of Vp8Dct1 .. Vp8Dct4: + absValue = token + of Vp8DctCat1 .. Vp8DctCat6: + let category = token - Vp8DctCat1 + var extra = 0 + for probability in Vp8ProbDctCat[category]: + if probability == 0: + break + extra = extra + extra + partition.readBool(probability).int + absValue = Vp8DctCatBase[category].int + extra + else: + failInvalid("invalid VP8 DCT token") + + skip = false + complexityState = + if absValue == 0: 0 + elif absValue == 1: 1 + else: 2 + + if partition.readFlag(): + absValue = -absValue + + let zigzag = Vp8Zigzag[i].int + coeffBlock[zigzag] = absValue * (if zigzag > 0: acq.int else: dcq.int) + result = true + +proc readResidualData( + decoder: Vp8Decoder, mb: Vp8MacroBlock, mbx, partitionIndex: int +): tuple[residue: array[384, int], nonZeroDct: bool] = + let segmentIndex = mb.segmentId.int + var + plane = if mb.lumaMode == Vp8BPred: 3 else: 1 + residue: array[384, int] + nonZeroDct = false + + if plane == 1: + let + complexity = decoder.top[mbx].complexity[0].int + + decoder.left.complexity[0].int + dcq = decoder.segments[segmentIndex].y2dc + acq = decoder.segments[segmentIndex].y2ac + var coeffBlock: array[16, int] + let hasCoefficients = decoder.readCoefficients( + coeffBlock, partitionIndex, plane, complexity, dcq, acq + ) + decoder.left.complexity[0] = hasCoefficients.uint8 + decoder.top[mbx].complexity[0] = hasCoefficients.uint8 + coeffBlock.iwht4x4() + for k in 0 ..< 16: + residue[16 * k] = coeffBlock[k] + plane = 0 + + for y in 0 ..< 4: + var left = decoder.left.complexity[y + 1] + for x in 0 ..< 4: + let + i = x + y * 4 + complexity = decoder.top[mbx].complexity[x + 1].int + left.int + dcq = decoder.segments[segmentIndex].ydc + acq = decoder.segments[segmentIndex].yac + var coeffBlock: array[16, int] + coeffBlock[0] = residue[i * 16] + let hasCoefficients = decoder.readCoefficients( + coeffBlock, partitionIndex, plane, complexity, dcq, acq + ) + if coeffBlock[0] != 0 or hasCoefficients: + nonZeroDct = true + coeffBlock.idct4x4() + left = hasCoefficients.uint8 + decoder.top[mbx].complexity[x + 1] = hasCoefficients.uint8 + for k in 0 ..< 16: + residue[i * 16 + k] = coeffBlock[k] + decoder.left.complexity[y + 1] = left + + plane = 2 + for j in [5, 7]: + for y in 0 ..< 2: + var left = decoder.left.complexity[y + j] + for x in 0 ..< 2: + let + i = x + y * 2 + (if j == 5: 16 else: 20) + complexity = decoder.top[mbx].complexity[x + j].int + left.int + dcq = decoder.segments[segmentIndex].uvdc + acq = decoder.segments[segmentIndex].uvac + var coeffBlock: array[16, int] + let hasCoefficients = decoder.readCoefficients( + coeffBlock, partitionIndex, plane, complexity, dcq, acq + ) + if coeffBlock[0] != 0 or hasCoefficients: + nonZeroDct = true + coeffBlock.idct4x4() + left = hasCoefficients.uint8 + decoder.top[mbx].complexity[x + j] = hasCoefficients.uint8 + for k in 0 ..< 16: + residue[i * 16 + k] = coeffBlock[k] + decoder.left.complexity[y + j] = left + + return (residue, nonZeroDct) + +proc setChromaBorder( + leftBorder, topBorder: var seq[uint8], + chromaBlock: openArray[uint8], + mbx: int +) = + leftBorder[0] = chromaBlock[8] + for i in 0 ..< 8: + leftBorder[1 + i] = chromaBlock[(i + 1) * Vp8ChromaStride + 8] + for i in 0 ..< 8: + topBorder[mbx * 8 + i] = chromaBlock[8 * Vp8ChromaStride + 1 + i] + +proc intraPredictLuma( + decoder: Vp8Decoder, + mbx, mby: int, + mb: Vp8MacroBlock, + residue: openArray[int] +) = + let lumaWidth = decoder.mbWidth * 16 + var ws = createBorderLuma( + mbx, mby, decoder.mbWidth, decoder.topBorderY, decoder.leftBorderY + ) + + case mb.lumaMode + of Vp8VPred: ws.predictVpred(16, 1, 1, Vp8LumaStride) + of Vp8HPred: ws.predictHpred(16, 1, 1, Vp8LumaStride) + of Vp8TmPred: ws.predictTmpred(16, 1, 1, Vp8LumaStride) + of Vp8DcPred: ws.predictDcpred(16, Vp8LumaStride, mby != 0, mbx != 0) + of Vp8BPred: ws.predict4x4(Vp8LumaStride, mb.bpred, residue) + else: failInvalid("invalid VP8 luma prediction mode") + + if mb.lumaMode != Vp8BPred: + for y in 0 ..< 4: + for x in 0 ..< 4: + let + i = x + y * 4 + y0 = 1 + y * 4 + x0 = 1 + x * 4 + ws.addResidue(residue, i * 16, y0, x0, Vp8LumaStride) + + decoder.leftBorderY[0] = ws[16] + for i in 0 ..< 16: + decoder.leftBorderY[1 + i] = ws[(i + 1) * Vp8LumaStride + 16] + for i in 0 ..< 16: + decoder.topBorderY[mbx * 16 + i] = ws[16 * Vp8LumaStride + 1 + i] + + for y in 0 ..< 16: + let + dst = (mby * 16 + y) * lumaWidth + mbx * 16 + src = (1 + y) * Vp8LumaStride + 1 + for x in 0 ..< 16: + decoder.frame.ybuf[dst + x] = ws[src + x] + +proc intraPredictChroma( + decoder: Vp8Decoder, + mbx, mby: int, + mb: Vp8MacroBlock, + residue: openArray[int] +) = + let chromaWidth = decoder.mbWidth * 8 + var + uws = createBorderChroma(mbx, mby, decoder.topBorderU, decoder.leftBorderU) + vws = createBorderChroma(mbx, mby, decoder.topBorderV, decoder.leftBorderV) + + case mb.chromaMode + of Vp8DcPred: + uws.predictDcpred(8, Vp8ChromaStride, mby != 0, mbx != 0) + vws.predictDcpred(8, Vp8ChromaStride, mby != 0, mbx != 0) + of Vp8VPred: + uws.predictVpred(8, 1, 1, Vp8ChromaStride) + vws.predictVpred(8, 1, 1, Vp8ChromaStride) + of Vp8HPred: + uws.predictHpred(8, 1, 1, Vp8ChromaStride) + vws.predictHpred(8, 1, 1, Vp8ChromaStride) + of Vp8TmPred: + uws.predictTmpred(8, 1, 1, Vp8ChromaStride) + vws.predictTmpred(8, 1, 1, Vp8ChromaStride) + else: + failInvalid("invalid VP8 chroma prediction mode") + + for y in 0 ..< 2: + for x in 0 ..< 2: + let + i = x + y * 2 + y0 = 1 + y * 4 + x0 = 1 + x * 4 + uws.addResidue(residue, (16 + i) * 16, y0, x0, Vp8ChromaStride) + vws.addResidue(residue, (20 + i) * 16, y0, x0, Vp8ChromaStride) + + setChromaBorder(decoder.leftBorderU, decoder.topBorderU, uws, mbx) + setChromaBorder(decoder.leftBorderV, decoder.topBorderV, vws, mbx) + + for y in 0 ..< 8: + let + dst = (mby * 8 + y) * chromaWidth + mbx * 8 + src = (1 + y) * Vp8ChromaStride + 1 + for x in 0 ..< 8: + decoder.frame.ubuf[dst + x] = uws[src + x] + decoder.frame.vbuf[dst + x] = vws[src + x] + +proc calculateFilterParameters( + decoder: Vp8Decoder, mb: Vp8MacroBlock +): tuple[filterLevel, interiorLimit, hevThreshold: uint8] = + let segment = decoder.segments[mb.segmentId.int] + var filterLevel = decoder.frame.filterLevel.int + if filterLevel == 0: + return (0.uint8, 0.uint8, 0.uint8) + + if decoder.segmentsEnabled: + if segment.deltaValues: + filterLevel += segment.loopfilterLevel.int + else: + filterLevel = segment.loopfilterLevel.int + filterLevel = max(0, min(63, filterLevel)) + + if decoder.loopFilterAdjustmentsEnabled: + filterLevel += decoder.refDelta[0] + if mb.lumaMode == Vp8BPred: + filterLevel += decoder.modeDelta[0] + filterLevel = max(0, min(63, filterLevel)) + + var interiorLimit = filterLevel + if decoder.frame.sharpnessLevel > 0: + interiorLimit = interiorLimit shr ( + if decoder.frame.sharpnessLevel > 4: 2 else: 1 + ) + let sharpLimit = 9 - decoder.frame.sharpnessLevel.int + if interiorLimit > sharpLimit: + interiorLimit = sharpLimit + if interiorLimit == 0: + interiorLimit = 1 + + let hevThreshold = + if filterLevel >= 40: 2 + elif filterLevel >= 15: 1 + else: 0 + + (filterLevel.uint8, interiorLimit.uint8, hevThreshold.uint8) + +proc loopFilter(decoder: Vp8Decoder, mbx, mby: int, mb: Vp8MacroBlock) = + let + lumaWidth = decoder.mbWidth * 16 + chromaWidth = decoder.mbWidth * 8 + params = decoder.calculateFilterParameters(mb) + if params.filterLevel == 0: + return + + let + macroblockEdgeLimit = + ((params.filterLevel + 2) * 2 + params.interiorLimit).uint8 + subblockEdgeLimit = + (params.filterLevel * 2 + params.interiorLimit).uint8 + doSubblockFiltering = + mb.lumaMode == Vp8BPred or (not mb.coeffsSkipped and mb.nonZeroDct) + + if mbx > 0: + if decoder.frame.filterType: + for y in 0 ..< 16: + let point = (mby * 16 + y) * lumaWidth + mbx * 16 + decoder.frame.ybuf.simpleSegmentHorizontal(macroblockEdgeLimit, point - 4) + else: + for y in 0 ..< 16: + let point = (mby * 16 + y) * lumaWidth + mbx * 16 + decoder.frame.ybuf.macroblockFilterHorizontal( + params.hevThreshold, params.interiorLimit, macroblockEdgeLimit, + point - 4 + ) + for y in 0 ..< 8: + let point = (mby * 8 + y) * chromaWidth + mbx * 8 + decoder.frame.ubuf.macroblockFilterHorizontal( + params.hevThreshold, params.interiorLimit, macroblockEdgeLimit, + point - 4 + ) + decoder.frame.vbuf.macroblockFilterHorizontal( + params.hevThreshold, params.interiorLimit, macroblockEdgeLimit, + point - 4 + ) + + if doSubblockFiltering: + if decoder.frame.filterType: + for x in countup(4, 12, 4): + for y in 0 ..< 16: + let point = (mby * 16 + y) * lumaWidth + mbx * 16 + x + decoder.frame.ybuf.simpleSegmentHorizontal(subblockEdgeLimit, point - 4) + else: + for x in countup(4, 12, 4): + for y in 0 ..< 16: + let point = (mby * 16 + y) * lumaWidth + mbx * 16 + x + decoder.frame.ybuf.subblockFilterHorizontal( + params.hevThreshold, params.interiorLimit, subblockEdgeLimit, + point - 4 + ) + for y in 0 ..< 8: + let point = (mby * 8 + y) * chromaWidth + mbx * 8 + 4 + decoder.frame.ubuf.subblockFilterHorizontal( + params.hevThreshold, params.interiorLimit, subblockEdgeLimit, point - 4 + ) + decoder.frame.vbuf.subblockFilterHorizontal( + params.hevThreshold, params.interiorLimit, subblockEdgeLimit, point - 4 + ) + + if mby > 0: + if decoder.frame.filterType: + for x in 0 ..< 16: + let point = (mby * 16) * lumaWidth + mbx * 16 + x + decoder.frame.ybuf.simpleSegmentVertical( + macroblockEdgeLimit, point, lumaWidth + ) + else: + for x in 0 ..< 16: + let point = (mby * 16) * lumaWidth + mbx * 16 + x + decoder.frame.ybuf.macroblockFilterVertical( + params.hevThreshold, params.interiorLimit, macroblockEdgeLimit, + point, lumaWidth + ) + for x in 0 ..< 8: + let point = (mby * 8) * chromaWidth + mbx * 8 + x + decoder.frame.ubuf.macroblockFilterVertical( + params.hevThreshold, params.interiorLimit, macroblockEdgeLimit, + point, chromaWidth + ) + decoder.frame.vbuf.macroblockFilterVertical( + params.hevThreshold, params.interiorLimit, macroblockEdgeLimit, + point, chromaWidth + ) + + if doSubblockFiltering: + if decoder.frame.filterType: + for y in countup(4, 12, 4): + for x in 0 ..< 16: + let point = (mby * 16 + y) * lumaWidth + mbx * 16 + x + decoder.frame.ybuf.simpleSegmentVertical(subblockEdgeLimit, point, lumaWidth) + else: + for y in countup(4, 12, 4): + for x in 0 ..< 16: + let point = (mby * 16 + y) * lumaWidth + mbx * 16 + x + decoder.frame.ybuf.subblockFilterVertical( + params.hevThreshold, params.interiorLimit, subblockEdgeLimit, + point, lumaWidth + ) + for x in 0 ..< 8: + let point = (mby * 8 + 4) * chromaWidth + mbx * 8 + x + decoder.frame.ubuf.subblockFilterVertical( + params.hevThreshold, params.interiorLimit, subblockEdgeLimit, + point, chromaWidth + ) + decoder.frame.vbuf.subblockFilterVertical( + params.hevThreshold, params.interiorLimit, subblockEdgeLimit, + point, chromaWidth + ) + +proc decodeVp8Frame( + data: string, offset, size: int, width, height: int +): Vp8Frame = + if size < 10: + failInvalid("truncated VP8 header") + + let frameTag = + data.readUint8Checked(offset + 0).uint32 or + (data.readUint8Checked(offset + 1).uint32 shl 8) or + (data.readUint8Checked(offset + 2).uint32 shl 16) + if (frameTag and 1) != 0: + failInvalid("VP8 image is not a key frame") + if data.readUint8Checked(offset + 3) != 0x9d or + data.readUint8Checked(offset + 4) != 0x01 or + data.readUint8Checked(offset + 5) != 0x2a: + failInvalid("invalid VP8 start code") + + let + firstPartitionSize = (frameTag shr 5).int + frameWidth = data.readUint16le(offset + 6) and 0x3fff + frameHeight = data.readUint16le(offset + 8) and 0x3fff + if frameWidth != width or frameHeight != height: + failInvalid("inconsistent VP8 dimensions") + if firstPartitionSize > size - 10: + failInvalid("truncated VP8 first partition") + + let decoder = newVp8Decoder() + decoder.frame.width = frameWidth + decoder.frame.height = frameHeight + decoder.frame.version = ((frameTag shr 1) and 7).uint8 + decoder.frame.forDisplay = ((frameTag shr 4) and 1) != 0 + decoder.mbWidth = (frameWidth + 15) div 16 + decoder.mbHeight = (frameHeight + 15) div 16 + + let + lumaWidth = decoder.mbWidth * 16 + chromaWidth = decoder.mbWidth * 8 + decoder.top = newSeq[Vp8PreviousMacroBlock](decoder.mbWidth) + decoder.frame.ybuf = newSeq[uint8](lumaWidth * decoder.mbHeight * 16) + decoder.frame.ubuf = newSeq[uint8](chromaWidth * decoder.mbHeight * 8) + decoder.frame.vbuf = newSeq[uint8](chromaWidth * decoder.mbHeight * 8) + decoder.topBorderY = newSeq[uint8](lumaWidth + 4) + decoder.leftBorderY = newSeq[uint8](17) + decoder.topBorderU = newSeq[uint8](chromaWidth) + decoder.leftBorderU = newSeq[uint8](9) + decoder.topBorderV = newSeq[uint8](chromaWidth) + decoder.leftBorderV = newSeq[uint8](9) + for i in 0 ..< decoder.topBorderY.len: + decoder.topBorderY[i] = 127 + for i in 0 ..< decoder.leftBorderY.len: + decoder.leftBorderY[i] = 129 + for i in 0 ..< decoder.topBorderU.len: + decoder.topBorderU[i] = 127 + decoder.topBorderV[i] = 127 + for i in 0 ..< decoder.leftBorderU.len: + decoder.leftBorderU[i] = 129 + decoder.leftBorderV[i] = 129 + + let + firstPartitionOffset = offset + 10 + tokenPartitionOffset = firstPartitionOffset + firstPartitionSize + chunkEnd = offset + size + decoder.b = newVp8BoolDecoder(data, firstPartitionOffset, firstPartitionSize) + + let colorSpace = decoder.b.readLiteral(1) + decoder.frame.pixelType = decoder.b.readLiteral(1) + if colorSpace != 0: + failInvalid("invalid VP8 color space") + + decoder.segmentsEnabled = decoder.b.readFlag() + if decoder.segmentsEnabled: + decoder.readSegmentUpdates() + + decoder.frame.filterType = decoder.b.readFlag() + decoder.frame.filterLevel = decoder.b.readLiteral(6) + decoder.frame.sharpnessLevel = decoder.b.readLiteral(3) + decoder.loopFilterAdjustmentsEnabled = decoder.b.readFlag() + if decoder.loopFilterAdjustmentsEnabled: + decoder.readLoopFilterAdjustments() + + decoder.numPartitions = 1 shl decoder.b.readLiteral(2).int + decoder.initPartitions( + data, tokenPartitionOffset, chunkEnd, decoder.numPartitions + ) + decoder.readQuantizationIndices() + discard decoder.b.readLiteral(1) + decoder.updateTokenProbabilities() + + decoder.hasProbSkipFalse = decoder.b.readLiteral(1) == 1 + if decoder.hasProbSkipFalse: + decoder.probSkipFalse = decoder.b.readLiteral(8) + + for mby in 0 ..< decoder.mbHeight: + let partitionIndex = mby mod decoder.numPartitions + decoder.left = Vp8PreviousMacroBlock() + for mbx in 0 ..< decoder.mbWidth: + var mb = decoder.readMacroblockHeader(mbx) + let residue = + if not mb.coeffsSkipped: + let residual = decoder.readResidualData(mb, mbx, partitionIndex) + mb.nonZeroDct = residual.nonZeroDct + residual.residue + else: + if mb.lumaMode != Vp8BPred: + decoder.left.complexity[0] = 0 + decoder.top[mbx].complexity[0] = 0 + for i in 1 ..< 9: + decoder.left.complexity[i] = 0 + decoder.top[mbx].complexity[i] = 0 + default(array[384, int]) + + decoder.intraPredictLuma(mbx, mby, mb, residue) + decoder.intraPredictChroma(mbx, mby, mb, residue) + decoder.macroblocks.add(mb) + + for i in 0 ..< decoder.leftBorderY.len: + decoder.leftBorderY[i] = 129 + for i in 0 ..< decoder.leftBorderU.len: + decoder.leftBorderU[i] = 129 + decoder.leftBorderV[i] = 129 + + for mby in 0 ..< decoder.mbHeight: + for mbx in 0 ..< decoder.mbWidth: + decoder.loopFilter( + mbx, mby, decoder.macroblocks[mby * decoder.mbWidth + mbx] + ) + + decoder.frame + +proc yuvMulhi(value: uint8, coefficient: int): int {.inline.} = + (value.int * coefficient) shr 8 + +proc yuvClip(value: int): uint8 {.inline.} = + if value < 0: + 0 + elif value > (256 shl 6) - 1: + 255 + else: + (value shr 6).uint8 + +proc yuvToR(y, v: uint8): uint8 {.inline.} = + yuvClip(yuvMulhi(y, 19077) + yuvMulhi(v, 26149) - 14234) + +proc yuvToG(y, u, v: uint8): uint8 {.inline.} = + yuvClip(yuvMulhi(y, 19077) - yuvMulhi(u, 6419) - + yuvMulhi(v, 13320) + 8708) + +proc yuvToB(y, u: uint8): uint8 {.inline.} = + yuvClip(yuvMulhi(y, 19077) + yuvMulhi(u, 33050) - 17685) + +proc getFancyChroma( + main, secondary1, secondary2, tertiary: uint8 +): uint8 {.inline.} = + ((9 * main.uint16 + 3 * secondary1.uint16 + 3 * secondary2.uint16 + + tertiary.uint16 + 8) div 16).uint8 + +proc frameToRgbaBytes(frame: Vp8Frame): seq[uint8] = + result = newSeq[uint8](frame.width * frame.height * 4) + let + lumaWidth = ((frame.width + 15) div 16) * 16 + chromaWidth = lumaWidth div 2 + chromaPixelWidth = (frame.width + 1) div 2 + chromaPixelHeight = (frame.height + 1) div 2 + for y in 0 ..< frame.height: + let + topOnly = y == 0 or (y == frame.height - 1 and (frame.height and 1) == 0) + mainRow = + if y == 0: 0 + elif topOnly: chromaPixelHeight - 1 + else: y div 2 + otherRow = + if topOnly: + mainRow + elif (y and 1) != 0: + min(chromaPixelHeight - 1, mainRow + 1) + else: + max(0, mainRow - 1) + mainBase = mainRow * chromaWidth + otherBase = otherRow * chromaWidth + for x in 0 ..< frame.width: + var + uValue: uint8 + vValue: uint8 + if x == 0: + if topOnly: + uValue = frame.ubuf[mainBase] + vValue = frame.vbuf[mainBase] + else: + uValue = getFancyChroma( + frame.ubuf[mainBase], frame.ubuf[mainBase], + frame.ubuf[otherBase], frame.ubuf[otherBase] + ) + vValue = getFancyChroma( + frame.vbuf[mainBase], frame.vbuf[mainBase], + frame.vbuf[otherBase], frame.vbuf[otherBase] + ) + elif (x and 1) != 0: + let + col = (x - 1) div 2 + nextCol = min(chromaPixelWidth - 1, col + 1) + uValue = getFancyChroma( + frame.ubuf[mainBase + col], frame.ubuf[mainBase + nextCol], + frame.ubuf[otherBase + col], frame.ubuf[otherBase + nextCol] + ) + vValue = getFancyChroma( + frame.vbuf[mainBase + col], frame.vbuf[mainBase + nextCol], + frame.vbuf[otherBase + col], frame.vbuf[otherBase + nextCol] + ) + else: + let + col = x div 2 + prevCol = max(0, col - 1) + uValue = getFancyChroma( + frame.ubuf[mainBase + col], frame.ubuf[mainBase + prevCol], + frame.ubuf[otherBase + col], frame.ubuf[otherBase + prevCol] + ) + vValue = getFancyChroma( + frame.vbuf[mainBase + col], frame.vbuf[mainBase + prevCol], + frame.vbuf[otherBase + col], frame.vbuf[otherBase + prevCol] + ) + + let + yValue = frame.ybuf[y * lumaWidth + x] + dst = (y * frame.width + x) * 4 + result[dst + 0] = yuvToR(yValue, vValue) + result[dst + 1] = yuvToG(yValue, uValue, vValue) + result[dst + 2] = yuvToB(yValue, uValue) + result[dst + 3] = 255 + +proc alphaPredictor( + alpha: seq[uint8], width, x, y, filterMethod: int +): uint8 = + case filterMethod + of 0: + 0 + of 1: + if x == 0 and y == 0: + 0 + elif x == 0: + alpha[(y - 1) * width + x] + else: + alpha[y * width + x - 1] + of 2: + if x == 0 and y == 0: + 0 + elif y == 0: + alpha[y * width + x - 1] + else: + alpha[(y - 1) * width + x] + of 3: + let (left, top, topLeft) = + if x == 0 and y == 0: + (0, 0, 0) + elif x == 0: + let value = alpha[(y - 1) * width + x].int + (value, value, value) + elif y == 0: + let value = alpha[y * width + x - 1].int + (value, value, value) + else: + ( + alpha[y * width + x - 1].int, + alpha[(y - 1) * width + x].int, + alpha[(y - 1) * width + x - 1].int + ) + max(0, min(255, left + top - topLeft)).uint8 + else: + failInvalid("invalid VP8 alpha filter") + +proc decodeAlphaData(data: string, info: WebpInfo): seq[uint8] = + if info.alphaSize < 1: + failInvalid("invalid ALPH chunk size") + if info.alphaInfo.preprocessing > 1: + failInvalid("invalid VP8 alpha preprocessing") + if info.alphaInfo.compressionMethod > 1: + failInvalid("invalid VP8 alpha compression") + + let + payloadOffset = info.alphaOffset + 1 + payloadSize = info.alphaSize - 1 + pixelCount = info.width * info.height + var residual: seq[uint8] + case info.alphaInfo.compressionMethod + of 0: + if payloadSize < pixelCount: + failInvalid("truncated VP8 alpha data") + data.checkBounds(payloadOffset, pixelCount) + residual = newSeq[uint8](pixelCount) + for i in 0 ..< pixelCount: + residual[i] = data.readUint8(payloadOffset + i) + of 1: + let rgbaData = decodeLosslessData( + data, payloadOffset, payloadSize, info.width, info.height, true + ) + residual = newSeq[uint8](pixelCount) + for i in 0 ..< pixelCount: + residual[i] = rgbaData[i * 4 + 1] + else: + failInvalid("invalid VP8 alpha compression") + + result = newSeq[uint8](pixelCount) + for y in 0 ..< info.height: + for x in 0 ..< info.width: + let index = y * info.width + x + result[index] = wrapByte( + result.alphaPredictor(info.width, x, y, + info.alphaInfo.filterMethod).int + + residual[index].int + ) + +proc parseVp8Dimensions( + data: string, offset, size: int, info: WebpInfo +) = + if size < 10: + failInvalid("truncated VP8 header") + + let frameTag = + data.readUint8Checked(offset + 0).uint32 or + (data.readUint8Checked(offset + 1).uint32 shl 8) or + (data.readUint8Checked(offset + 2).uint32 shl 16) + let frameType = frameTag and 1 + if frameType != 0: + failInvalid("VP8 image is not a key frame") + + info.vp8Version = ((frameTag shr 1) and 0x7).int + info.vp8ShowFrame = ((frameTag shr 4) and 1) != 0 + + if data.readUint8Checked(offset + 3) != 0x9d or + data.readUint8Checked(offset + 4) != 0x01 or + data.readUint8Checked(offset + 5) != 0x2a: + failInvalid("invalid VP8 start code") + + let + width = data.readUint16le(offset + 6) and 0x3fff + height = data.readUint16le(offset + 8) and 0x3fff + checkImageSize(width, height) + if not info.hasVp8X: + info.width = width + info.height = height + +proc parseVp8LDimensions( + data: string, offset, size: int, info: WebpInfo +) = + if size < 5: + failInvalid("truncated VP8L header") + if data.readUint8Checked(offset) != 0x2f: + failInvalid("invalid VP8L signature") + + let bits = data.readUint32le(offset + 1) + let + width = ((bits and 0x3fff) + 1).int + height = (((bits shr 14) and 0x3fff) + 1).int + version = ((bits shr 29) and 0x7).int + if version != 0: + failInvalid("invalid VP8L version") + + checkImageSize(width, height) + info.losslessAlpha = ((bits shr 28) and 1) != 0 + if not info.hasVp8X: + info.width = width + info.height = height + +proc parseVp8X(data: string, offset, size: int, info: WebpInfo) = + if size != 10: + failInvalid("invalid VP8X chunk size") + + let flags = data.readUint8Checked(offset) + info.hasIccp = (flags and 0b00100000) != 0 + info.hasAlpha = (flags and 0b00010000) != 0 + info.hasExif = (flags and 0b00001000) != 0 + info.hasXmp = (flags and 0b00000100) != 0 + info.hasAnimation = (flags and 0b00000010) != 0 + + let + width = data.readUint24le(offset + 4) + 1 + height = data.readUint24le(offset + 7) + 1 + checkImageSize(width, height) + info.hasVp8X = true + info.width = width + info.height = height + +proc parseAlpha(data: string, offset, size: int, info: WebpInfo) = + if size < 1: + failInvalid("invalid ALPH chunk size") + let flags = data.readUint8Checked(offset) + info.alphaInfo.compressionMethod = (flags and 0b00000011).int + info.alphaInfo.filterMethod = ((flags shr 2) and 0b00000011).int + info.alphaInfo.preprocessing = ((flags shr 4) and 0b00000011).int + info.hasAlpha = true + +proc parseAnim(data: string, offset, size: int, info: WebpInfo) = + if size < 6: + failInvalid("invalid ANIM chunk size") + let bg = data.readUint32le(offset) + info.backgroundColor = rgba( + ((bg shr 16) and 0xff).uint8, + ((bg shr 8) and 0xff).uint8, + (bg and 0xff).uint8, + ((bg shr 24) and 0xff).uint8 + ) + info.loopCount = data.readUint16le(offset + 4) + info.hasAnimation = true + +proc parseAnimFrame(data: string, offset, size: int, info: WebpInfo) = + if size < 16: + failInvalid("invalid ANMF chunk size") + inc info.frameCount + +proc decodeWebpInfo*(data: string): WebpInfo {.raises: [PixieError].} = + ## Decodes WebP container and image-header information. + if data.len < 12: + failInvalid("truncated RIFF header") + if data.readStrChecked(0, 4) != WebpRiffSignature: + failInvalid("missing RIFF signature") + if data.readStrChecked(8, 4) != WebpSignature: + failInvalid("missing WEBP signature") + + result = WebpInfo() + let riffSize = data.readUint32le(4).int + if riffSize < 4: + failInvalid("invalid RIFF size") + if riffSize > data.len - 8: + failInvalid("truncated RIFF data") + result.fileSize = riffSize + 8 + + var pos = 12 + while pos < result.fileSize: + if pos + 8 > result.fileSize: + failInvalid("truncated chunk header") + + let + fourcc = data.readStrChecked(pos, 4) + size = data.readUint32le(pos + 4).int + payloadOffset = pos + 8 + if size > result.fileSize - payloadOffset: + failInvalid("truncated " & fourcc & " chunk") + + result.chunks.add(WebpChunkInfo( + kind: fourcc.chunkKind(), + fourcc: fourcc, + offset: payloadOffset, + size: size + )) + + case fourcc + of WebpVp8Signature: + result.compression = LossyWebp + result.vp8Offset = payloadOffset + result.vp8Size = size + data.parseVp8Dimensions(payloadOffset, size, result) + of WebpVp8LSignature: + result.compression = LosslessWebp + result.vp8LOffset = payloadOffset + result.vp8LSize = size + data.parseVp8LDimensions(payloadOffset, size, result) + of WebpVp8XSignature: + data.parseVp8X(payloadOffset, size, result) + of WebpAlphaSignature: + result.alphaOffset = payloadOffset + result.alphaSize = size + data.parseAlpha(payloadOffset, size, result) + of WebpAnimSignature: + data.parseAnim(payloadOffset, size, result) + of WebpAnimFrameSignature: + data.parseAnimFrame(payloadOffset, size, result) + of WebpIccpSignature: + result.iccpOffset = payloadOffset + result.iccpSize = size + result.hasIccp = true + of WebpExifSignature: + result.exifOffset = payloadOffset + result.exifSize = size + result.hasExif = true + of WebpXmpSignature: + result.xmpOffset = payloadOffset + result.xmpSize = size + result.hasXmp = true + else: + discard + + pos = payloadOffset + size + if (size and 1) != 0: + if pos >= result.fileSize: + failInvalid("missing chunk padding") + inc pos + + checkImageSize(result.width, result.height) + if result.compression == UnknownWebpCompression and result.frameCount == 0: + failInvalid("missing image bitstream") + +proc decodeWebpInfo*( + data: pointer, len: int +): WebpInfo {.raises: [PixieError].} = + ## Decodes WebP container and image-header information from memory. + if len <= 0: + failInvalid("empty buffer") + var s = newString(len) + copyMem(s[0].addr, data, len) + decodeWebpInfo(s) + +proc decodeWebpDimensions*( + data: string +): ImageDimensions {.raises: [PixieError].} = + ## Decodes the WebP dimensions. + let info = decodeWebpInfo(data) + result.width = info.width + result.height = info.height + +proc decodeWebpDimensions*( + data: pointer, len: int +): ImageDimensions {.raises: [PixieError].} = + ## Decodes the WebP dimensions. + let info = decodeWebpInfo(data, len) + result.width = info.width + result.height = info.height + +proc decodeWebp*(data: string): Image {.raises: [PixieError].} = + ## Decodes a WebP image. + let info = decodeWebpInfo(data) + case info.compression + of LosslessWebp: + var rgbaData = decodeLosslessData( + data, info.vp8LOffset, info.vp8LSize, info.width, info.height, false + ) + if not info.losslessAlpha: + for i in countup(3, rgbaData.len - 1, 4): + rgbaData[i] = 255 + newImageFromRgbaBytes(rgbaData, info.width, info.height) + of LossyWebp: + var rgbaData = frameToRgbaBytes( + decodeVp8Frame(data, info.vp8Offset, info.vp8Size, info.width, info.height) + ) + if info.hasAlpha: + if info.alphaOffset == 0: + failInvalid("missing ALPH chunk") + let alpha = decodeAlphaData(data, info) + for i in 0 ..< info.width * info.height: + rgbaData[i * 4 + 3] = alpha[i] + newImageFromRgbaBytes(rgbaData, info.width, info.height) + of UnknownWebpCompression: + raise newException(PixieError, "Invalid WebP, animation decoding is not implemented") + +{.pop.} + +when defined(release): + {.pop.} diff --git a/src/pixie/fileformats/webp_vp8_tables.nim b/src/pixie/fileformats/webp_vp8_tables.nim new file mode 100644 index 00000000..cffbc70e --- /dev/null +++ b/src/pixie/fileformats/webp_vp8_tables.nim @@ -0,0 +1,538 @@ +# VP8 probability and quantization tables used by the WebP lossy decoder. +# Values are from the VP8 bitstream specification tables. + +type + Vp8TokenProbTables* = array[4, array[8, array[3, array[11, uint8]]]] + +const + Vp8SegmentIdTree* = [2, 4, 0, -1, -2, -3] + Vp8KeyframeYModeTree* = [-4, 2, 4, 6, 0, -1, -2, -3] + Vp8KeyframeYModeProbs* = [145.uint8, 156, 163, 128] + Vp8KeyframeBpredModeTree* = [ + 0, 2, -1, 4, -2, 6, 8, 12, -3, 10, -5, -6, -4, 14, -7, + 16, -8, -9 + ] + Vp8KeyframeBpredModeProbs*: array[10, array[10, array[9, uint8]]] = + [ + [ + [231, 120, 48, 89, 115, 113, 120, 152, 112], + [152, 179, 64, 126, 170, 118, 46, 70, 95], + [175, 69, 143, 80, 85, 82, 72, 155, 103], + [56, 58, 10, 171, 218, 189, 17, 13, 152], + [144, 71, 10, 38, 171, 213, 144, 34, 26], + [114, 26, 17, 163, 44, 195, 21, 10, 173], + [121, 24, 80, 195, 26, 62, 44, 64, 85], + [170, 46, 55, 19, 136, 160, 33, 206, 71], + [63, 20, 8, 114, 114, 208, 12, 9, 226], + [81, 40, 11, 96, 182, 84, 29, 16, 36], + ], + [ + [134, 183, 89, 137, 98, 101, 106, 165, 148], + [72, 187, 100, 130, 157, 111, 32, 75, 80], + [66, 102, 167, 99, 74, 62, 40, 234, 128], + [41, 53, 9, 178, 241, 141, 26, 8, 107], + [104, 79, 12, 27, 217, 255, 87, 17, 7], + [74, 43, 26, 146, 73, 166, 49, 23, 157], + [65, 38, 105, 160, 51, 52, 31, 115, 128], + [87, 68, 71, 44, 114, 51, 15, 186, 23], + [47, 41, 14, 110, 182, 183, 21, 17, 194], + [66, 45, 25, 102, 197, 189, 23, 18, 22], + ], + [ + [88, 88, 147, 150, 42, 46, 45, 196, 205], + [43, 97, 183, 117, 85, 38, 35, 179, 61], + [39, 53, 200, 87, 26, 21, 43, 232, 171], + [56, 34, 51, 104, 114, 102, 29, 93, 77], + [107, 54, 32, 26, 51, 1, 81, 43, 31], + [39, 28, 85, 171, 58, 165, 90, 98, 64], + [34, 22, 116, 206, 23, 34, 43, 166, 73], + [68, 25, 106, 22, 64, 171, 36, 225, 114], + [34, 19, 21, 102, 132, 188, 16, 76, 124], + [62, 18, 78, 95, 85, 57, 50, 48, 51], + ], + [ + [193, 101, 35, 159, 215, 111, 89, 46, 111], + [60, 148, 31, 172, 219, 228, 21, 18, 111], + [112, 113, 77, 85, 179, 255, 38, 120, 114], + [40, 42, 1, 196, 245, 209, 10, 25, 109], + [100, 80, 8, 43, 154, 1, 51, 26, 71], + [88, 43, 29, 140, 166, 213, 37, 43, 154], + [61, 63, 30, 155, 67, 45, 68, 1, 209], + [142, 78, 78, 16, 255, 128, 34, 197, 171], + [41, 40, 5, 102, 211, 183, 4, 1, 221], + [51, 50, 17, 168, 209, 192, 23, 25, 82], + ], + [ + [125, 98, 42, 88, 104, 85, 117, 175, 82], + [95, 84, 53, 89, 128, 100, 113, 101, 45], + [75, 79, 123, 47, 51, 128, 81, 171, 1], + [57, 17, 5, 71, 102, 57, 53, 41, 49], + [115, 21, 2, 10, 102, 255, 166, 23, 6], + [38, 33, 13, 121, 57, 73, 26, 1, 85], + [41, 10, 67, 138, 77, 110, 90, 47, 114], + [101, 29, 16, 10, 85, 128, 101, 196, 26], + [57, 18, 10, 102, 102, 213, 34, 20, 43], + [117, 20, 15, 36, 163, 128, 68, 1, 26], + ], + [ + [138, 31, 36, 171, 27, 166, 38, 44, 229], + [67, 87, 58, 169, 82, 115, 26, 59, 179], + [63, 59, 90, 180, 59, 166, 93, 73, 154], + [40, 40, 21, 116, 143, 209, 34, 39, 175], + [57, 46, 22, 24, 128, 1, 54, 17, 37], + [47, 15, 16, 183, 34, 223, 49, 45, 183], + [46, 17, 33, 183, 6, 98, 15, 32, 183], + [65, 32, 73, 115, 28, 128, 23, 128, 205], + [40, 3, 9, 115, 51, 192, 18, 6, 223], + [87, 37, 9, 115, 59, 77, 64, 21, 47], + ], + [ + [104, 55, 44, 218, 9, 54, 53, 130, 226], + [64, 90, 70, 205, 40, 41, 23, 26, 57], + [54, 57, 112, 184, 5, 41, 38, 166, 213], + [30, 34, 26, 133, 152, 116, 10, 32, 134], + [75, 32, 12, 51, 192, 255, 160, 43, 51], + [39, 19, 53, 221, 26, 114, 32, 73, 255], + [31, 9, 65, 234, 2, 15, 1, 118, 73], + [88, 31, 35, 67, 102, 85, 55, 186, 85], + [56, 21, 23, 111, 59, 205, 45, 37, 192], + [55, 38, 70, 124, 73, 102, 1, 34, 98], + ], + [ + [102, 61, 71, 37, 34, 53, 31, 243, 192], + [69, 60, 71, 38, 73, 119, 28, 222, 37], + [68, 45, 128, 34, 1, 47, 11, 245, 171], + [62, 17, 19, 70, 146, 85, 55, 62, 70], + [75, 15, 9, 9, 64, 255, 184, 119, 16], + [37, 43, 37, 154, 100, 163, 85, 160, 1], + [63, 9, 92, 136, 28, 64, 32, 201, 85], + [86, 6, 28, 5, 64, 255, 25, 248, 1], + [56, 8, 17, 132, 137, 255, 55, 116, 128], + [58, 15, 20, 82, 135, 57, 26, 121, 40], + ], + [ + [164, 50, 31, 137, 154, 133, 25, 35, 218], + [51, 103, 44, 131, 131, 123, 31, 6, 158], + [86, 40, 64, 135, 148, 224, 45, 183, 128], + [22, 26, 17, 131, 240, 154, 14, 1, 209], + [83, 12, 13, 54, 192, 255, 68, 47, 28], + [45, 16, 21, 91, 64, 222, 7, 1, 197], + [56, 21, 39, 155, 60, 138, 23, 102, 213], + [85, 26, 85, 85, 128, 128, 32, 146, 171], + [18, 11, 7, 63, 144, 171, 4, 4, 246], + [35, 27, 10, 146, 174, 171, 12, 26, 128], + ], + [ + [190, 80, 35, 99, 180, 80, 126, 54, 45], + [85, 126, 47, 87, 176, 51, 41, 20, 32], + [101, 75, 128, 139, 118, 146, 116, 128, 85], + [56, 41, 15, 176, 236, 85, 37, 9, 62], + [146, 36, 19, 30, 171, 255, 97, 27, 20], + [71, 30, 17, 119, 118, 255, 17, 18, 138], + [101, 38, 60, 138, 55, 70, 43, 26, 142], + [138, 45, 61, 62, 219, 1, 81, 188, 64], + [32, 41, 20, 117, 151, 142, 20, 21, 163], + [112, 19, 12, 61, 195, 128, 48, 4, 24], + ], + ] + Vp8KeyframeUvModeTree* = [0, 2, -1, 4, -2, -3] + Vp8KeyframeUvModeProbs* = [142.uint8, 114, 183] + Vp8CoeffUpdateProbs*: Vp8TokenProbTables = + [ + [ + [ + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [223, 241, 252, 255, 255, 255, 255, 255, 255, 255, 255], + [249, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255], + [234, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255], + [253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255], + [239, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255], + [254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255], + [251, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255], + [251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255], + [254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 254, 253, 255, 254, 255, 255, 255, 255, 255, 255], + [250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255], + [254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + ], + ], + [ + [ + [217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255], + [234, 250, 241, 250, 253, 255, 253, 254, 255, 255, 255], + ], + [ + [255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255], + [238, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255], + [249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [247, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255], + [252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255], + [253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255], + [250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + ], + ], + [ + [ + [186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255], + [234, 251, 244, 254, 255, 255, 255, 255, 255, 255, 255], + [251, 251, 243, 253, 254, 255, 254, 255, 255, 255, 255], + ], + [ + [255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255], + [236, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255], + [251, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255], + [254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + ], + ], + [ + [ + [248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255], + [248, 254, 249, 253, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255], + [246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255], + [252, 254, 251, 254, 254, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 254, 252, 255, 255, 255, 255, 255, 255, 255, 255], + [248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255], + [253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255], + [245, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255], + [253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255], + [252, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [249, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255], + [250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + ], + [ + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + ], + ], + ] + Vp8CoeffProbs*: Vp8TokenProbTables = + [ + [ + [ + [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128], + [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128], + [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128], + ], + [ + [253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128], + [189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128], + [106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128], + ], + [ + [1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128], + [181, 133, 238, 254, 221, 234, 255, 154, 128, 128, 128], + [78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128], + ], + [ + [1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128], + [184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128], + [77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128], + ], + [ + [1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128], + [170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128], + [37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128], + ], + [ + [1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128], + [207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128], + [102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128], + ], + [ + [1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128], + [177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128], + [80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128], + ], + [ + [1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128], + [246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128], + [255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128], + ], + ], + [ + [ + [198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62], + [131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1], + [68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128], + ], + [ + [1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128], + [184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128], + [81, 99, 181, 242, 176, 190, 249, 202, 255, 255, 128], + ], + [ + [1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128], + [99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128], + [23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128], + ], + [ + [1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128], + [109, 178, 241, 255, 231, 245, 255, 255, 128, 128, 128], + [44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128], + ], + [ + [1, 132, 239, 251, 219, 209, 255, 165, 128, 128, 128], + [94, 136, 225, 251, 218, 190, 255, 255, 128, 128, 128], + [22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128], + ], + [ + [1, 182, 249, 255, 232, 235, 128, 128, 128, 128, 128], + [124, 143, 241, 255, 227, 234, 128, 128, 128, 128, 128], + [35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128], + ], + [ + [1, 157, 247, 255, 236, 231, 255, 255, 128, 128, 128], + [121, 141, 235, 255, 225, 227, 255, 255, 128, 128, 128], + [45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128], + ], + [ + [1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128], + [203, 1, 248, 255, 255, 128, 128, 128, 128, 128, 128], + [137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128], + ], + ], + [ + [ + [253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128], + [175, 13, 224, 243, 193, 185, 249, 198, 255, 255, 128], + [73, 17, 171, 221, 161, 179, 236, 167, 255, 234, 128], + ], + [ + [1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128], + [239, 90, 244, 250, 211, 209, 255, 255, 128, 128, 128], + [155, 77, 195, 248, 188, 195, 255, 255, 128, 128, 128], + ], + [ + [1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128], + [201, 51, 219, 255, 196, 186, 128, 128, 128, 128, 128], + [69, 46, 190, 239, 201, 218, 255, 228, 128, 128, 128], + ], + [ + [1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128], + [223, 165, 249, 255, 213, 255, 128, 128, 128, 128, 128], + [141, 124, 248, 255, 255, 128, 128, 128, 128, 128, 128], + ], + [ + [1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128], + [190, 36, 230, 255, 236, 255, 128, 128, 128, 128, 128], + [149, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128], + ], + [ + [1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128], + [247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128], + [240, 128, 255, 128, 128, 128, 128, 128, 128, 128, 128], + ], + [ + [1, 134, 252, 255, 255, 128, 128, 128, 128, 128, 128], + [213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128], + [55, 93, 255, 128, 128, 128, 128, 128, 128, 128, 128], + ], + [ + [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128], + [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128], + [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128], + ], + ], + [ + [ + [202, 24, 213, 235, 186, 191, 220, 160, 240, 175, 255], + [126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128], + [61, 46, 138, 219, 151, 178, 240, 170, 255, 216, 128], + ], + [ + [1, 112, 230, 250, 199, 191, 247, 159, 255, 255, 128], + [166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128], + [39, 77, 162, 232, 172, 180, 245, 178, 255, 255, 128], + ], + [ + [1, 52, 220, 246, 198, 199, 249, 220, 255, 255, 128], + [124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128], + [24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128], + ], + [ + [1, 182, 225, 249, 219, 240, 255, 224, 128, 128, 128], + [149, 150, 226, 252, 216, 205, 255, 171, 128, 128, 128], + [28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128], + ], + [ + [1, 81, 230, 252, 204, 203, 255, 192, 128, 128, 128], + [123, 102, 209, 247, 188, 196, 255, 233, 128, 128, 128], + [20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128], + ], + [ + [1, 222, 248, 255, 216, 213, 128, 128, 128, 128, 128], + [168, 175, 246, 252, 235, 205, 255, 255, 128, 128, 128], + [47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128], + ], + [ + [1, 121, 236, 253, 212, 214, 255, 255, 128, 128, 128], + [141, 84, 213, 252, 201, 202, 255, 219, 128, 128, 128], + [42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128], + ], + [ + [1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128], + [244, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128], + [238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128], + ], + ], + ] + Vp8DctTokenTree* = [ + -11, 2, 0, 4, -1, 6, 8, 12, -2, 10, -3, -4, 14, 16, + -5, -6, 18, 20, -7, -8, -9, -10 + ] + Vp8ProbDctCat*: array[6, array[12, uint8]] = + [ + [159, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [165, 145, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [173, 148, 140, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [176, 155, 140, 135, 0, 0, 0, 0, 0, 0, 0, 0], + [180, 157, 141, 134, 130, 0, 0, 0, 0, 0, 0, 0], + [254, 254, 243, 230, 196, 177, 153, 140, 133, 130, 129, 0], + ] + Vp8DctCatBase*: array[6, uint8] = + [5, 7, 11, 19, 35, 67] + Vp8CoeffBands*: array[16, uint8] = + [0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7] + Vp8DcQuant*: array[128, int16] = + [ + 4, 5, 6, 7, 8, 9, 10, 10, + 11, 12, 13, 14, 15, 16, 17, 17, + 18, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 25, 25, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, + 37, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 69, 70, 71, 72, 73, 74, + 75, 76, 76, 77, 78, 79, 80, 81, + 82, 83, 84, 85, 86, 87, 88, 89, + 91, 93, 95, 96, 98, 100, 101, 102, + 104, 106, 108, 110, 112, 114, 116, 118, + 122, 124, 126, 128, 130, 132, 134, 136, + 138, 140, 143, 145, 148, 151, 154, 157, + ] + Vp8AcQuant*: array[128, int16] = + [ + 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 60, + 62, 64, 66, 68, 70, 72, 74, 76, + 78, 80, 82, 84, 86, 88, 90, 92, + 94, 96, 98, 100, 102, 104, 106, 108, + 110, 112, 114, 116, 119, 122, 125, 128, + 131, 134, 137, 140, 143, 146, 149, 152, + 155, 158, 161, 164, 167, 170, 173, 177, + 181, 185, 189, 193, 197, 201, 205, 209, + 213, 217, 221, 225, 229, 234, 239, 245, + 249, 254, 259, 264, 269, 274, 279, 284, + ] + Vp8Zigzag*: array[16, uint8] = + [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15] diff --git a/tests/fileformats/webp/SOURCE.txt b/tests/fileformats/webp/SOURCE.txt new file mode 100644 index 00000000..058af9b7 --- /dev/null +++ b/tests/fileformats/webp/SOURCE.txt @@ -0,0 +1,6 @@ +Fixtures in this directory are copied from the WebM Project libwebp test data: + +https://chromium.googlesource.com/webm/libwebp-test-data + +The two very large upstream files, bryce.webp and lossless_big_random_alpha.webp, +are intentionally not copied into Pixie's test fixtures. diff --git a/tests/fileformats/webp/alpha_color_cache.webp b/tests/fileformats/webp/alpha_color_cache.webp new file mode 100644 index 00000000..7d10c871 Binary files /dev/null and b/tests/fileformats/webp/alpha_color_cache.webp differ diff --git a/tests/fileformats/webp/alpha_filter_0_method_0.webp b/tests/fileformats/webp/alpha_filter_0_method_0.webp new file mode 100644 index 00000000..b935b5c5 Binary files /dev/null and b/tests/fileformats/webp/alpha_filter_0_method_0.webp differ diff --git a/tests/fileformats/webp/alpha_filter_0_method_1.webp b/tests/fileformats/webp/alpha_filter_0_method_1.webp new file mode 100644 index 00000000..b333bee8 Binary files /dev/null and b/tests/fileformats/webp/alpha_filter_0_method_1.webp differ diff --git a/tests/fileformats/webp/alpha_filter_1.webp b/tests/fileformats/webp/alpha_filter_1.webp new file mode 100644 index 00000000..fcda70e8 Binary files /dev/null and b/tests/fileformats/webp/alpha_filter_1.webp differ diff --git a/tests/fileformats/webp/alpha_filter_1_method_0.webp b/tests/fileformats/webp/alpha_filter_1_method_0.webp new file mode 100644 index 00000000..009131c0 Binary files /dev/null and b/tests/fileformats/webp/alpha_filter_1_method_0.webp differ diff --git a/tests/fileformats/webp/alpha_filter_1_method_1.webp b/tests/fileformats/webp/alpha_filter_1_method_1.webp new file mode 100644 index 00000000..bd9bf2f2 Binary files /dev/null and b/tests/fileformats/webp/alpha_filter_1_method_1.webp differ diff --git a/tests/fileformats/webp/alpha_filter_2.webp b/tests/fileformats/webp/alpha_filter_2.webp new file mode 100644 index 00000000..13f77f51 Binary files /dev/null and b/tests/fileformats/webp/alpha_filter_2.webp differ diff --git a/tests/fileformats/webp/alpha_filter_2_method_0.webp b/tests/fileformats/webp/alpha_filter_2_method_0.webp new file mode 100644 index 00000000..0e3ec8dc Binary files /dev/null and b/tests/fileformats/webp/alpha_filter_2_method_0.webp differ diff --git a/tests/fileformats/webp/alpha_filter_2_method_1.webp b/tests/fileformats/webp/alpha_filter_2_method_1.webp new file mode 100644 index 00000000..57135236 Binary files /dev/null and b/tests/fileformats/webp/alpha_filter_2_method_1.webp differ diff --git a/tests/fileformats/webp/alpha_filter_3.webp b/tests/fileformats/webp/alpha_filter_3.webp new file mode 100644 index 00000000..6624e553 Binary files /dev/null and b/tests/fileformats/webp/alpha_filter_3.webp differ diff --git a/tests/fileformats/webp/alpha_filter_3_method_0.webp b/tests/fileformats/webp/alpha_filter_3_method_0.webp new file mode 100644 index 00000000..f49e5579 Binary files /dev/null and b/tests/fileformats/webp/alpha_filter_3_method_0.webp differ diff --git a/tests/fileformats/webp/alpha_filter_3_method_1.webp b/tests/fileformats/webp/alpha_filter_3_method_1.webp new file mode 100644 index 00000000..271ab892 Binary files /dev/null and b/tests/fileformats/webp/alpha_filter_3_method_1.webp differ diff --git a/tests/fileformats/webp/alpha_no_compression.webp b/tests/fileformats/webp/alpha_no_compression.webp new file mode 100644 index 00000000..021572fb Binary files /dev/null and b/tests/fileformats/webp/alpha_no_compression.webp differ diff --git a/tests/fileformats/webp/bad_palette_index.webp b/tests/fileformats/webp/bad_palette_index.webp new file mode 100644 index 00000000..89064d81 Binary files /dev/null and b/tests/fileformats/webp/bad_palette_index.webp differ diff --git a/tests/fileformats/webp/big_endian_bug_393.webp b/tests/fileformats/webp/big_endian_bug_393.webp new file mode 100644 index 00000000..41893001 Binary files /dev/null and b/tests/fileformats/webp/big_endian_bug_393.webp differ diff --git a/tests/fileformats/webp/bug3.webp b/tests/fileformats/webp/bug3.webp new file mode 100644 index 00000000..0b59987e Binary files /dev/null and b/tests/fileformats/webp/bug3.webp differ diff --git a/tests/fileformats/webp/color_cache_bits_11.webp b/tests/fileformats/webp/color_cache_bits_11.webp new file mode 100644 index 00000000..0404b9ca Binary files /dev/null and b/tests/fileformats/webp/color_cache_bits_11.webp differ diff --git a/tests/fileformats/webp/dual_transform.webp b/tests/fileformats/webp/dual_transform.webp new file mode 100644 index 00000000..40b5775a Binary files /dev/null and b/tests/fileformats/webp/dual_transform.webp differ diff --git a/tests/fileformats/webp/lossless1.webp b/tests/fileformats/webp/lossless1.webp new file mode 100644 index 00000000..c0cdebf5 Binary files /dev/null and b/tests/fileformats/webp/lossless1.webp differ diff --git a/tests/fileformats/webp/lossless2.webp b/tests/fileformats/webp/lossless2.webp new file mode 100644 index 00000000..7b20fe01 Binary files /dev/null and b/tests/fileformats/webp/lossless2.webp differ diff --git a/tests/fileformats/webp/lossless3.webp b/tests/fileformats/webp/lossless3.webp new file mode 100644 index 00000000..56a4f9fb Binary files /dev/null and b/tests/fileformats/webp/lossless3.webp differ diff --git a/tests/fileformats/webp/lossless4.webp b/tests/fileformats/webp/lossless4.webp new file mode 100644 index 00000000..b7a77b5c Binary files /dev/null and b/tests/fileformats/webp/lossless4.webp differ diff --git a/tests/fileformats/webp/lossless_color_transform.webp b/tests/fileformats/webp/lossless_color_transform.webp new file mode 100644 index 00000000..2cb8a962 Binary files /dev/null and b/tests/fileformats/webp/lossless_color_transform.webp differ diff --git a/tests/fileformats/webp/lossless_vec_1_0.webp b/tests/fileformats/webp/lossless_vec_1_0.webp new file mode 100644 index 00000000..5da0ae20 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_1_0.webp differ diff --git a/tests/fileformats/webp/lossless_vec_1_1.webp b/tests/fileformats/webp/lossless_vec_1_1.webp new file mode 100644 index 00000000..426af4a2 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_1_1.webp differ diff --git a/tests/fileformats/webp/lossless_vec_1_10.webp b/tests/fileformats/webp/lossless_vec_1_10.webp new file mode 100644 index 00000000..89147a0b Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_1_10.webp differ diff --git a/tests/fileformats/webp/lossless_vec_1_11.webp b/tests/fileformats/webp/lossless_vec_1_11.webp new file mode 100644 index 00000000..b465dd32 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_1_11.webp differ diff --git a/tests/fileformats/webp/lossless_vec_1_12.webp b/tests/fileformats/webp/lossless_vec_1_12.webp new file mode 100644 index 00000000..fabe293f Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_1_12.webp differ diff --git a/tests/fileformats/webp/lossless_vec_1_13.webp b/tests/fileformats/webp/lossless_vec_1_13.webp new file mode 100644 index 00000000..684ca664 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_1_13.webp differ diff --git a/tests/fileformats/webp/lossless_vec_1_14.webp b/tests/fileformats/webp/lossless_vec_1_14.webp new file mode 100644 index 00000000..667fce94 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_1_14.webp differ diff --git a/tests/fileformats/webp/lossless_vec_1_15.webp b/tests/fileformats/webp/lossless_vec_1_15.webp new file mode 100644 index 00000000..2dd011fe Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_1_15.webp differ diff --git a/tests/fileformats/webp/lossless_vec_1_2.webp b/tests/fileformats/webp/lossless_vec_1_2.webp new file mode 100644 index 00000000..4f8160b1 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_1_2.webp differ diff --git a/tests/fileformats/webp/lossless_vec_1_3.webp b/tests/fileformats/webp/lossless_vec_1_3.webp new file mode 100644 index 00000000..58718668 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_1_3.webp differ diff --git a/tests/fileformats/webp/lossless_vec_1_4.webp b/tests/fileformats/webp/lossless_vec_1_4.webp new file mode 100644 index 00000000..9de86d8f Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_1_4.webp differ diff --git a/tests/fileformats/webp/lossless_vec_1_5.webp b/tests/fileformats/webp/lossless_vec_1_5.webp new file mode 100644 index 00000000..1bd1e8c6 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_1_5.webp differ diff --git a/tests/fileformats/webp/lossless_vec_1_6.webp b/tests/fileformats/webp/lossless_vec_1_6.webp new file mode 100644 index 00000000..24fd10ff Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_1_6.webp differ diff --git a/tests/fileformats/webp/lossless_vec_1_7.webp b/tests/fileformats/webp/lossless_vec_1_7.webp new file mode 100644 index 00000000..f5e313e3 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_1_7.webp differ diff --git a/tests/fileformats/webp/lossless_vec_1_8.webp b/tests/fileformats/webp/lossless_vec_1_8.webp new file mode 100644 index 00000000..9b718c0d Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_1_8.webp differ diff --git a/tests/fileformats/webp/lossless_vec_1_9.webp b/tests/fileformats/webp/lossless_vec_1_9.webp new file mode 100644 index 00000000..a35d7061 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_1_9.webp differ diff --git a/tests/fileformats/webp/lossless_vec_2_0.webp b/tests/fileformats/webp/lossless_vec_2_0.webp new file mode 100644 index 00000000..288a232c Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_2_0.webp differ diff --git a/tests/fileformats/webp/lossless_vec_2_1.webp b/tests/fileformats/webp/lossless_vec_2_1.webp new file mode 100644 index 00000000..658f9da9 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_2_1.webp differ diff --git a/tests/fileformats/webp/lossless_vec_2_10.webp b/tests/fileformats/webp/lossless_vec_2_10.webp new file mode 100644 index 00000000..f7d5315b Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_2_10.webp differ diff --git a/tests/fileformats/webp/lossless_vec_2_11.webp b/tests/fileformats/webp/lossless_vec_2_11.webp new file mode 100644 index 00000000..94171acb Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_2_11.webp differ diff --git a/tests/fileformats/webp/lossless_vec_2_12.webp b/tests/fileformats/webp/lossless_vec_2_12.webp new file mode 100644 index 00000000..8e935921 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_2_12.webp differ diff --git a/tests/fileformats/webp/lossless_vec_2_13.webp b/tests/fileformats/webp/lossless_vec_2_13.webp new file mode 100644 index 00000000..9e1b06a3 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_2_13.webp differ diff --git a/tests/fileformats/webp/lossless_vec_2_14.webp b/tests/fileformats/webp/lossless_vec_2_14.webp new file mode 100644 index 00000000..3c897a6a Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_2_14.webp differ diff --git a/tests/fileformats/webp/lossless_vec_2_15.webp b/tests/fileformats/webp/lossless_vec_2_15.webp new file mode 100644 index 00000000..adaec208 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_2_15.webp differ diff --git a/tests/fileformats/webp/lossless_vec_2_2.webp b/tests/fileformats/webp/lossless_vec_2_2.webp new file mode 100644 index 00000000..dd8ca3aa Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_2_2.webp differ diff --git a/tests/fileformats/webp/lossless_vec_2_3.webp b/tests/fileformats/webp/lossless_vec_2_3.webp new file mode 100644 index 00000000..944655c1 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_2_3.webp differ diff --git a/tests/fileformats/webp/lossless_vec_2_4.webp b/tests/fileformats/webp/lossless_vec_2_4.webp new file mode 100644 index 00000000..ac609768 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_2_4.webp differ diff --git a/tests/fileformats/webp/lossless_vec_2_5.webp b/tests/fileformats/webp/lossless_vec_2_5.webp new file mode 100644 index 00000000..c5fe0616 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_2_5.webp differ diff --git a/tests/fileformats/webp/lossless_vec_2_6.webp b/tests/fileformats/webp/lossless_vec_2_6.webp new file mode 100644 index 00000000..88ba9352 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_2_6.webp differ diff --git a/tests/fileformats/webp/lossless_vec_2_7.webp b/tests/fileformats/webp/lossless_vec_2_7.webp new file mode 100644 index 00000000..6f074bb6 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_2_7.webp differ diff --git a/tests/fileformats/webp/lossless_vec_2_8.webp b/tests/fileformats/webp/lossless_vec_2_8.webp new file mode 100644 index 00000000..37c0bb66 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_2_8.webp differ diff --git a/tests/fileformats/webp/lossless_vec_2_9.webp b/tests/fileformats/webp/lossless_vec_2_9.webp new file mode 100644 index 00000000..3f5f4332 Binary files /dev/null and b/tests/fileformats/webp/lossless_vec_2_9.webp differ diff --git a/tests/fileformats/webp/lossy_alpha1.webp b/tests/fileformats/webp/lossy_alpha1.webp new file mode 100644 index 00000000..14fe79dc Binary files /dev/null and b/tests/fileformats/webp/lossy_alpha1.webp differ diff --git a/tests/fileformats/webp/lossy_alpha2.webp b/tests/fileformats/webp/lossy_alpha2.webp new file mode 100644 index 00000000..97e0ff1a Binary files /dev/null and b/tests/fileformats/webp/lossy_alpha2.webp differ diff --git a/tests/fileformats/webp/lossy_alpha3.webp b/tests/fileformats/webp/lossy_alpha3.webp new file mode 100644 index 00000000..f2899df5 Binary files /dev/null and b/tests/fileformats/webp/lossy_alpha3.webp differ diff --git a/tests/fileformats/webp/lossy_alpha4.webp b/tests/fileformats/webp/lossy_alpha4.webp new file mode 100644 index 00000000..0f99d2ab Binary files /dev/null and b/tests/fileformats/webp/lossy_alpha4.webp differ diff --git a/tests/fileformats/webp/lossy_extreme_probabilities.webp b/tests/fileformats/webp/lossy_extreme_probabilities.webp new file mode 100644 index 00000000..90793ac8 Binary files /dev/null and b/tests/fileformats/webp/lossy_extreme_probabilities.webp differ diff --git a/tests/fileformats/webp/lossy_q0_f100.webp b/tests/fileformats/webp/lossy_q0_f100.webp new file mode 100644 index 00000000..984194e8 Binary files /dev/null and b/tests/fileformats/webp/lossy_q0_f100.webp differ diff --git a/tests/fileformats/webp/near_lossless_75.webp b/tests/fileformats/webp/near_lossless_75.webp new file mode 100644 index 00000000..8aa6a720 Binary files /dev/null and b/tests/fileformats/webp/near_lossless_75.webp differ diff --git a/tests/fileformats/webp/one_color_no_palette.webp b/tests/fileformats/webp/one_color_no_palette.webp new file mode 100644 index 00000000..4410fb9f Binary files /dev/null and b/tests/fileformats/webp/one_color_no_palette.webp differ diff --git a/tests/fileformats/webp/segment01.webp b/tests/fileformats/webp/segment01.webp new file mode 100644 index 00000000..29ec6b36 Binary files /dev/null and b/tests/fileformats/webp/segment01.webp differ diff --git a/tests/fileformats/webp/segment02.webp b/tests/fileformats/webp/segment02.webp new file mode 100644 index 00000000..65d1de4e Binary files /dev/null and b/tests/fileformats/webp/segment02.webp differ diff --git a/tests/fileformats/webp/segment03.webp b/tests/fileformats/webp/segment03.webp new file mode 100644 index 00000000..1c262192 Binary files /dev/null and b/tests/fileformats/webp/segment03.webp differ diff --git a/tests/fileformats/webp/small_13x1.webp b/tests/fileformats/webp/small_13x1.webp new file mode 100644 index 00000000..632a349a Binary files /dev/null and b/tests/fileformats/webp/small_13x1.webp differ diff --git a/tests/fileformats/webp/small_1x1.webp b/tests/fileformats/webp/small_1x1.webp new file mode 100644 index 00000000..47c92827 Binary files /dev/null and b/tests/fileformats/webp/small_1x1.webp differ diff --git a/tests/fileformats/webp/small_1x13.webp b/tests/fileformats/webp/small_1x13.webp new file mode 100644 index 00000000..8049cac2 Binary files /dev/null and b/tests/fileformats/webp/small_1x13.webp differ diff --git a/tests/fileformats/webp/small_31x13.webp b/tests/fileformats/webp/small_31x13.webp new file mode 100644 index 00000000..ab2f7e6d Binary files /dev/null and b/tests/fileformats/webp/small_31x13.webp differ diff --git a/tests/fileformats/webp/test-nostrong.webp b/tests/fileformats/webp/test-nostrong.webp new file mode 100644 index 00000000..0b6f81a8 Binary files /dev/null and b/tests/fileformats/webp/test-nostrong.webp differ diff --git a/tests/fileformats/webp/test.webp b/tests/fileformats/webp/test.webp new file mode 100644 index 00000000..c4a7b16c Binary files /dev/null and b/tests/fileformats/webp/test.webp differ diff --git a/tests/fileformats/webp/very_short.webp b/tests/fileformats/webp/very_short.webp new file mode 100644 index 00000000..128f6e74 Binary files /dev/null and b/tests/fileformats/webp/very_short.webp differ diff --git a/tests/fileformats/webp/vp80-00-comprehensive-001.webp b/tests/fileformats/webp/vp80-00-comprehensive-001.webp new file mode 100644 index 00000000..86a2a35f Binary files /dev/null and b/tests/fileformats/webp/vp80-00-comprehensive-001.webp differ diff --git a/tests/fileformats/webp/vp80-00-comprehensive-002.webp b/tests/fileformats/webp/vp80-00-comprehensive-002.webp new file mode 100644 index 00000000..f2914d51 Binary files /dev/null and b/tests/fileformats/webp/vp80-00-comprehensive-002.webp differ diff --git a/tests/fileformats/webp/vp80-00-comprehensive-003.webp b/tests/fileformats/webp/vp80-00-comprehensive-003.webp new file mode 100644 index 00000000..90eca21c Binary files /dev/null and b/tests/fileformats/webp/vp80-00-comprehensive-003.webp differ diff --git a/tests/fileformats/webp/vp80-00-comprehensive-004.webp b/tests/fileformats/webp/vp80-00-comprehensive-004.webp new file mode 100644 index 00000000..817d1f41 Binary files /dev/null and b/tests/fileformats/webp/vp80-00-comprehensive-004.webp differ diff --git a/tests/fileformats/webp/vp80-00-comprehensive-005.webp b/tests/fileformats/webp/vp80-00-comprehensive-005.webp new file mode 100644 index 00000000..d20aa126 Binary files /dev/null and b/tests/fileformats/webp/vp80-00-comprehensive-005.webp differ diff --git a/tests/fileformats/webp/vp80-00-comprehensive-006.webp b/tests/fileformats/webp/vp80-00-comprehensive-006.webp new file mode 100644 index 00000000..857d77af Binary files /dev/null and b/tests/fileformats/webp/vp80-00-comprehensive-006.webp differ diff --git a/tests/fileformats/webp/vp80-00-comprehensive-007.webp b/tests/fileformats/webp/vp80-00-comprehensive-007.webp new file mode 100644 index 00000000..d08dc6c1 Binary files /dev/null and b/tests/fileformats/webp/vp80-00-comprehensive-007.webp differ diff --git a/tests/fileformats/webp/vp80-00-comprehensive-008.webp b/tests/fileformats/webp/vp80-00-comprehensive-008.webp new file mode 100644 index 00000000..3e67e82f Binary files /dev/null and b/tests/fileformats/webp/vp80-00-comprehensive-008.webp differ diff --git a/tests/fileformats/webp/vp80-00-comprehensive-009.webp b/tests/fileformats/webp/vp80-00-comprehensive-009.webp new file mode 100644 index 00000000..603a1c50 Binary files /dev/null and b/tests/fileformats/webp/vp80-00-comprehensive-009.webp differ diff --git a/tests/fileformats/webp/vp80-00-comprehensive-010.webp b/tests/fileformats/webp/vp80-00-comprehensive-010.webp new file mode 100644 index 00000000..ddbd3385 Binary files /dev/null and b/tests/fileformats/webp/vp80-00-comprehensive-010.webp differ diff --git a/tests/fileformats/webp/vp80-00-comprehensive-011.webp b/tests/fileformats/webp/vp80-00-comprehensive-011.webp new file mode 100644 index 00000000..cabdf6dd Binary files /dev/null and b/tests/fileformats/webp/vp80-00-comprehensive-011.webp differ diff --git a/tests/fileformats/webp/vp80-00-comprehensive-012.webp b/tests/fileformats/webp/vp80-00-comprehensive-012.webp new file mode 100644 index 00000000..a7a45810 Binary files /dev/null and b/tests/fileformats/webp/vp80-00-comprehensive-012.webp differ diff --git a/tests/fileformats/webp/vp80-00-comprehensive-013.webp b/tests/fileformats/webp/vp80-00-comprehensive-013.webp new file mode 100644 index 00000000..bcda6c22 Binary files /dev/null and b/tests/fileformats/webp/vp80-00-comprehensive-013.webp differ diff --git a/tests/fileformats/webp/vp80-00-comprehensive-014.webp b/tests/fileformats/webp/vp80-00-comprehensive-014.webp new file mode 100644 index 00000000..c5d06cc6 Binary files /dev/null and b/tests/fileformats/webp/vp80-00-comprehensive-014.webp differ diff --git a/tests/fileformats/webp/vp80-00-comprehensive-015.webp b/tests/fileformats/webp/vp80-00-comprehensive-015.webp new file mode 100644 index 00000000..bd8bd679 Binary files /dev/null and b/tests/fileformats/webp/vp80-00-comprehensive-015.webp differ diff --git a/tests/fileformats/webp/vp80-00-comprehensive-016.webp b/tests/fileformats/webp/vp80-00-comprehensive-016.webp new file mode 100644 index 00000000..645e8c21 Binary files /dev/null and b/tests/fileformats/webp/vp80-00-comprehensive-016.webp differ diff --git a/tests/fileformats/webp/vp80-00-comprehensive-017.webp b/tests/fileformats/webp/vp80-00-comprehensive-017.webp new file mode 100644 index 00000000..9b4a4e19 Binary files /dev/null and b/tests/fileformats/webp/vp80-00-comprehensive-017.webp differ diff --git a/tests/fileformats/webp/vp80-01-intra-1400.webp b/tests/fileformats/webp/vp80-01-intra-1400.webp new file mode 100644 index 00000000..29815bf9 Binary files /dev/null and b/tests/fileformats/webp/vp80-01-intra-1400.webp differ diff --git a/tests/fileformats/webp/vp80-01-intra-1411.webp b/tests/fileformats/webp/vp80-01-intra-1411.webp new file mode 100644 index 00000000..5cb99d38 Binary files /dev/null and b/tests/fileformats/webp/vp80-01-intra-1411.webp differ diff --git a/tests/fileformats/webp/vp80-01-intra-1416.webp b/tests/fileformats/webp/vp80-01-intra-1416.webp new file mode 100644 index 00000000..bdceeafe Binary files /dev/null and b/tests/fileformats/webp/vp80-01-intra-1416.webp differ diff --git a/tests/fileformats/webp/vp80-01-intra-1417.webp b/tests/fileformats/webp/vp80-01-intra-1417.webp new file mode 100644 index 00000000..44155a84 Binary files /dev/null and b/tests/fileformats/webp/vp80-01-intra-1417.webp differ diff --git a/tests/fileformats/webp/vp80-02-inter-1402.webp b/tests/fileformats/webp/vp80-02-inter-1402.webp new file mode 100644 index 00000000..b1f4a955 Binary files /dev/null and b/tests/fileformats/webp/vp80-02-inter-1402.webp differ diff --git a/tests/fileformats/webp/vp80-02-inter-1412.webp b/tests/fileformats/webp/vp80-02-inter-1412.webp new file mode 100644 index 00000000..6e8d7230 Binary files /dev/null and b/tests/fileformats/webp/vp80-02-inter-1412.webp differ diff --git a/tests/fileformats/webp/vp80-02-inter-1418.webp b/tests/fileformats/webp/vp80-02-inter-1418.webp new file mode 100644 index 00000000..46f8c064 Binary files /dev/null and b/tests/fileformats/webp/vp80-02-inter-1418.webp differ diff --git a/tests/fileformats/webp/vp80-02-inter-1424.webp b/tests/fileformats/webp/vp80-02-inter-1424.webp new file mode 100644 index 00000000..8591fa25 Binary files /dev/null and b/tests/fileformats/webp/vp80-02-inter-1424.webp differ diff --git a/tests/fileformats/webp/vp80-03-segmentation-1401.webp b/tests/fileformats/webp/vp80-03-segmentation-1401.webp new file mode 100644 index 00000000..82f8b52d Binary files /dev/null and b/tests/fileformats/webp/vp80-03-segmentation-1401.webp differ diff --git a/tests/fileformats/webp/vp80-03-segmentation-1403.webp b/tests/fileformats/webp/vp80-03-segmentation-1403.webp new file mode 100644 index 00000000..826d34c6 Binary files /dev/null and b/tests/fileformats/webp/vp80-03-segmentation-1403.webp differ diff --git a/tests/fileformats/webp/vp80-03-segmentation-1407.webp b/tests/fileformats/webp/vp80-03-segmentation-1407.webp new file mode 100644 index 00000000..ba932875 Binary files /dev/null and b/tests/fileformats/webp/vp80-03-segmentation-1407.webp differ diff --git a/tests/fileformats/webp/vp80-03-segmentation-1408.webp b/tests/fileformats/webp/vp80-03-segmentation-1408.webp new file mode 100644 index 00000000..c5b8d9ae Binary files /dev/null and b/tests/fileformats/webp/vp80-03-segmentation-1408.webp differ diff --git a/tests/fileformats/webp/vp80-03-segmentation-1409.webp b/tests/fileformats/webp/vp80-03-segmentation-1409.webp new file mode 100644 index 00000000..530868df Binary files /dev/null and b/tests/fileformats/webp/vp80-03-segmentation-1409.webp differ diff --git a/tests/fileformats/webp/vp80-03-segmentation-1410.webp b/tests/fileformats/webp/vp80-03-segmentation-1410.webp new file mode 100644 index 00000000..bdd26505 Binary files /dev/null and b/tests/fileformats/webp/vp80-03-segmentation-1410.webp differ diff --git a/tests/fileformats/webp/vp80-03-segmentation-1413.webp b/tests/fileformats/webp/vp80-03-segmentation-1413.webp new file mode 100644 index 00000000..34c8633b Binary files /dev/null and b/tests/fileformats/webp/vp80-03-segmentation-1413.webp differ diff --git a/tests/fileformats/webp/vp80-03-segmentation-1414.webp b/tests/fileformats/webp/vp80-03-segmentation-1414.webp new file mode 100644 index 00000000..a183487b Binary files /dev/null and b/tests/fileformats/webp/vp80-03-segmentation-1414.webp differ diff --git a/tests/fileformats/webp/vp80-03-segmentation-1415.webp b/tests/fileformats/webp/vp80-03-segmentation-1415.webp new file mode 100644 index 00000000..4a35aadc Binary files /dev/null and b/tests/fileformats/webp/vp80-03-segmentation-1415.webp differ diff --git a/tests/fileformats/webp/vp80-03-segmentation-1425.webp b/tests/fileformats/webp/vp80-03-segmentation-1425.webp new file mode 100644 index 00000000..3315390a Binary files /dev/null and b/tests/fileformats/webp/vp80-03-segmentation-1425.webp differ diff --git a/tests/fileformats/webp/vp80-03-segmentation-1426.webp b/tests/fileformats/webp/vp80-03-segmentation-1426.webp new file mode 100644 index 00000000..4821dad0 Binary files /dev/null and b/tests/fileformats/webp/vp80-03-segmentation-1426.webp differ diff --git a/tests/fileformats/webp/vp80-03-segmentation-1427.webp b/tests/fileformats/webp/vp80-03-segmentation-1427.webp new file mode 100644 index 00000000..62083bea Binary files /dev/null and b/tests/fileformats/webp/vp80-03-segmentation-1427.webp differ diff --git a/tests/fileformats/webp/vp80-03-segmentation-1432.webp b/tests/fileformats/webp/vp80-03-segmentation-1432.webp new file mode 100644 index 00000000..2393fa59 Binary files /dev/null and b/tests/fileformats/webp/vp80-03-segmentation-1432.webp differ diff --git a/tests/fileformats/webp/vp80-03-segmentation-1435.webp b/tests/fileformats/webp/vp80-03-segmentation-1435.webp new file mode 100644 index 00000000..5c74d7e6 Binary files /dev/null and b/tests/fileformats/webp/vp80-03-segmentation-1435.webp differ diff --git a/tests/fileformats/webp/vp80-03-segmentation-1436.webp b/tests/fileformats/webp/vp80-03-segmentation-1436.webp new file mode 100644 index 00000000..04a074db Binary files /dev/null and b/tests/fileformats/webp/vp80-03-segmentation-1436.webp differ diff --git a/tests/fileformats/webp/vp80-03-segmentation-1437.webp b/tests/fileformats/webp/vp80-03-segmentation-1437.webp new file mode 100644 index 00000000..5c760a32 Binary files /dev/null and b/tests/fileformats/webp/vp80-03-segmentation-1437.webp differ diff --git a/tests/fileformats/webp/vp80-03-segmentation-1441.webp b/tests/fileformats/webp/vp80-03-segmentation-1441.webp new file mode 100644 index 00000000..0a32c73b Binary files /dev/null and b/tests/fileformats/webp/vp80-03-segmentation-1441.webp differ diff --git a/tests/fileformats/webp/vp80-03-segmentation-1442.webp b/tests/fileformats/webp/vp80-03-segmentation-1442.webp new file mode 100644 index 00000000..c0a95309 Binary files /dev/null and b/tests/fileformats/webp/vp80-03-segmentation-1442.webp differ diff --git a/tests/fileformats/webp/vp80-04-partitions-1404.webp b/tests/fileformats/webp/vp80-04-partitions-1404.webp new file mode 100644 index 00000000..91ed1f08 Binary files /dev/null and b/tests/fileformats/webp/vp80-04-partitions-1404.webp differ diff --git a/tests/fileformats/webp/vp80-04-partitions-1405.webp b/tests/fileformats/webp/vp80-04-partitions-1405.webp new file mode 100644 index 00000000..2c94ce4c Binary files /dev/null and b/tests/fileformats/webp/vp80-04-partitions-1405.webp differ diff --git a/tests/fileformats/webp/vp80-04-partitions-1406.webp b/tests/fileformats/webp/vp80-04-partitions-1406.webp new file mode 100644 index 00000000..41509957 Binary files /dev/null and b/tests/fileformats/webp/vp80-04-partitions-1406.webp differ diff --git a/tests/fileformats/webp/vp80-05-sharpness-1428.webp b/tests/fileformats/webp/vp80-05-sharpness-1428.webp new file mode 100644 index 00000000..b1713a69 Binary files /dev/null and b/tests/fileformats/webp/vp80-05-sharpness-1428.webp differ diff --git a/tests/fileformats/webp/vp80-05-sharpness-1429.webp b/tests/fileformats/webp/vp80-05-sharpness-1429.webp new file mode 100644 index 00000000..a81bfcf5 Binary files /dev/null and b/tests/fileformats/webp/vp80-05-sharpness-1429.webp differ diff --git a/tests/fileformats/webp/vp80-05-sharpness-1430.webp b/tests/fileformats/webp/vp80-05-sharpness-1430.webp new file mode 100644 index 00000000..66806df9 Binary files /dev/null and b/tests/fileformats/webp/vp80-05-sharpness-1430.webp differ diff --git a/tests/fileformats/webp/vp80-05-sharpness-1431.webp b/tests/fileformats/webp/vp80-05-sharpness-1431.webp new file mode 100644 index 00000000..ac3d0395 Binary files /dev/null and b/tests/fileformats/webp/vp80-05-sharpness-1431.webp differ diff --git a/tests/fileformats/webp/vp80-05-sharpness-1433.webp b/tests/fileformats/webp/vp80-05-sharpness-1433.webp new file mode 100644 index 00000000..818c0b9b Binary files /dev/null and b/tests/fileformats/webp/vp80-05-sharpness-1433.webp differ diff --git a/tests/fileformats/webp/vp80-05-sharpness-1434.webp b/tests/fileformats/webp/vp80-05-sharpness-1434.webp new file mode 100644 index 00000000..cc96d522 Binary files /dev/null and b/tests/fileformats/webp/vp80-05-sharpness-1434.webp differ diff --git a/tests/fileformats/webp/vp80-05-sharpness-1438.webp b/tests/fileformats/webp/vp80-05-sharpness-1438.webp new file mode 100644 index 00000000..a6e3fcf1 Binary files /dev/null and b/tests/fileformats/webp/vp80-05-sharpness-1438.webp differ diff --git a/tests/fileformats/webp/vp80-05-sharpness-1439.webp b/tests/fileformats/webp/vp80-05-sharpness-1439.webp new file mode 100644 index 00000000..9db92603 Binary files /dev/null and b/tests/fileformats/webp/vp80-05-sharpness-1439.webp differ diff --git a/tests/fileformats/webp/vp80-05-sharpness-1440.webp b/tests/fileformats/webp/vp80-05-sharpness-1440.webp new file mode 100644 index 00000000..2d64746a Binary files /dev/null and b/tests/fileformats/webp/vp80-05-sharpness-1440.webp differ diff --git a/tests/fileformats/webp/vp80-05-sharpness-1443.webp b/tests/fileformats/webp/vp80-05-sharpness-1443.webp new file mode 100644 index 00000000..8be0799e Binary files /dev/null and b/tests/fileformats/webp/vp80-05-sharpness-1443.webp differ diff --git a/tests/fuzz_webp.nim b/tests/fuzz_webp.nim new file mode 100644 index 00000000..8b8f6c3b --- /dev/null +++ b/tests/fuzz_webp.nim @@ -0,0 +1,40 @@ +import pixie/common, pixie/fileformats/webp, random, strformat, webpsuite + +const Iterations = + when defined(fuzzShort): 100 + else: 10_000 + +randomize() + +proc checkDimensions(data: string) = + try: + let dimensions = decodeWebpDimensions(data) + doAssert dimensions.width > 0 and dimensions.height > 0 + + let info = decodeWebpInfo(data) + doAssert info.width > 0 and info.height > 0 + except PixieError: + discard + +proc checkDecode(data: string) = + try: + let img = decodeWebp(data) + doAssert img.width > 0 and img.height > 0 + except PixieError: + discard + +for i in 0 ..< Iterations: + let file = WebpSuiteFiles[rand(WebpSuiteFiles.len - 1)] + var data = readFile(file) + let + pos = rand(0 ..< data.len) + bit = uint8(1 shl rand(7)) + value = data[pos].uint8 xor bit + data[pos] = value.char + echo &"{i} {file} {pos} {value}" + checkDimensions(data) + checkDecode(data) + + data = data[0 ..< pos] + checkDimensions(data) + checkDecode(data) diff --git a/tests/test_webp.nim b/tests/test_webp.nim new file mode 100644 index 00000000..0fd110f5 --- /dev/null +++ b/tests/test_webp.nim @@ -0,0 +1,97 @@ +import pixie, pixie/fileformats/webp, webpsuite + +proc checkDimensions(path: string, width, height: int) = + let + data = readFile(path) + info = decodeWebpInfo(data) + dimensions = decodeWebpDimensions(data) + pointerDimensions = decodeWebpDimensions(data.cstring, data.len) + readDimensions = readImageDimensions(path) + + doAssert info.width == width + doAssert info.height == height + doAssert dimensions.width == width + doAssert dimensions.height == height + doAssert pointerDimensions.width == width + doAssert pointerDimensions.height == height + doAssert readDimensions.width == width + doAssert readDimensions.height == height + +block: + doAssert WebpSuiteFiles.len == 129 + + var + lossyCount, losslessCount, alphaCount, vp8XCount: int + + for path in WebpSuiteFiles: + let + data = readFile(path) + info = decodeWebpInfo(data) + dimensions = decodeWebpDimensions(data) + + doAssert info.width == dimensions.width + doAssert info.height == dimensions.height + doAssert info.fileSize <= data.len + doAssert info.chunks.len > 0 + + case info.compression + of LossyWebp: + inc lossyCount + doAssert info.vp8Offset > 0 + doAssert info.vp8Size > 0 + let image = readImage(path) + doAssert image.width == info.width + doAssert image.height == info.height + of LosslessWebp: + inc losslessCount + doAssert info.vp8LOffset > 0 + doAssert info.vp8LSize > 0 + let image = readImage(path) + doAssert image.width == info.width + doAssert image.height == info.height + of UnknownWebpCompression: + doAssert info.hasAnimation + + if info.hasAlpha: + inc alphaCount + if info.hasVp8X: + inc vp8XCount + + doAssert lossyCount > 0 + doAssert losslessCount > 0 + doAssert alphaCount > 0 + doAssert vp8XCount > 0 + +checkDimensions("tests/fileformats/webp/small_1x1.webp", 1, 1) +checkDimensions("tests/fileformats/webp/small_1x13.webp", 1, 13) +checkDimensions("tests/fileformats/webp/small_13x1.webp", 13, 1) +checkDimensions("tests/fileformats/webp/small_31x13.webp", 31, 13) +checkDimensions("tests/fileformats/webp/test.webp", 128, 128) +checkDimensions("tests/fileformats/webp/lossy_q0_f100.webp", 128, 128) +checkDimensions("tests/fileformats/webp/lossless1.webp", 1000, 307) +checkDimensions("tests/fileformats/webp/lossy_alpha1.webp", 1000, 307) +checkDimensions("tests/fileformats/webp/alpha_no_compression.webp", 16, 16) +checkDimensions("tests/fileformats/webp/one_color_no_palette.webp", 100, 100) +checkDimensions("tests/fileformats/webp/near_lossless_75.webp", 256, 256) +checkDimensions("tests/fileformats/webp/vp80-00-comprehensive-016.webp", 176, 144) + +block: + let lossless = decodeWebpInfo(readFile("tests/fileformats/webp/lossless1.webp")) + doAssert lossless.compression == LosslessWebp + doAssert lossless.losslessAlpha + +block: + let lossyAlpha = decodeWebpInfo(readFile("tests/fileformats/webp/lossy_alpha1.webp")) + doAssert lossyAlpha.compression == LossyWebp + doAssert lossyAlpha.hasVp8X + doAssert lossyAlpha.hasAlpha + doAssert lossyAlpha.alphaOffset > 0 + doAssert lossyAlpha.alphaInfo.compressionMethod in [0, 1] + doAssert lossyAlpha.alphaInfo.filterMethod in 0 .. 3 + +block: + let rawAlpha = decodeWebpInfo( + readFile("tests/fileformats/webp/alpha_no_compression.webp") + ) + doAssert rawAlpha.hasAlpha + doAssert rawAlpha.alphaInfo.compressionMethod == 0 diff --git a/tests/tests.nim b/tests/tests.nim index 74b443a8..002bfeeb 100644 --- a/tests/tests.nim +++ b/tests/tests.nim @@ -12,6 +12,7 @@ import test_png, test_ppm, test_qoi, + test_webp, test_svg, xrays diff --git a/tests/validate_webp.nim b/tests/validate_webp.nim new file mode 100644 index 00000000..2d3b6ec0 --- /dev/null +++ b/tests/validate_webp.nim @@ -0,0 +1,54 @@ +import os, osproc, pixie, pixie/fileformats/webp, strformat, strutils, webpsuite + +const + GeneratedDir = "tests/fileformats/webp/generated" + DiffDir = "tests/fileformats/webp/diffs" + +createDir(GeneratedDir) +createDir(DiffDir) + +var + checked, skipped, failed: int + +for file in WebpSuiteFiles: + let info = decodeWebpInfo(readFile(file)) + if info.compression == UnknownWebpCompression: + inc skipped + continue + + inc checked + let + name = file.splitFile.name + genFile = GeneratedDir / (name & ".png") + magickFile = GeneratedDir / (name & ".magick.png") + diffFile = DiffDir / (name & ".diff.png") + + try: + let img = readImage(file) + img.writeFile(genFile) + + if execShellCmd( + "magick " & quoteShell(file) & " PNG32:" & quoteShell(magickFile) + ) != 0: + echo "ImageMagick failed: " & file + inc failed + continue + + let magickImg = readImage(magickFile) + let (score, diff) = magickImg.diff(img) + diff.writeFile(diffFile) + + if score > 0: + echo &"FAIL {score:2.6f}% ... {file}" + inc failed + else: + echo &"{score:2.6f}% ... {file}" + except PixieError as e: + echo "PixieError: " & file & ": " & e.msg + inc failed + +echo &"WebP validate: {checked - failed}/{checked} still images passed, " & + &"{skipped} animations skipped" + +if failed > 0: + quit(1) diff --git a/tests/webpsuite.nim b/tests/webpsuite.nim new file mode 100644 index 00000000..af990352 --- /dev/null +++ b/tests/webpsuite.nim @@ -0,0 +1,131 @@ +const WebpSuiteFiles* = [ + "tests/fileformats/webp/alpha_color_cache.webp", + "tests/fileformats/webp/alpha_filter_0_method_0.webp", + "tests/fileformats/webp/alpha_filter_0_method_1.webp", + "tests/fileformats/webp/alpha_filter_1.webp", + "tests/fileformats/webp/alpha_filter_1_method_0.webp", + "tests/fileformats/webp/alpha_filter_1_method_1.webp", + "tests/fileformats/webp/alpha_filter_2.webp", + "tests/fileformats/webp/alpha_filter_2_method_0.webp", + "tests/fileformats/webp/alpha_filter_2_method_1.webp", + "tests/fileformats/webp/alpha_filter_3.webp", + "tests/fileformats/webp/alpha_filter_3_method_0.webp", + "tests/fileformats/webp/alpha_filter_3_method_1.webp", + "tests/fileformats/webp/alpha_no_compression.webp", + "tests/fileformats/webp/bad_palette_index.webp", + "tests/fileformats/webp/big_endian_bug_393.webp", + "tests/fileformats/webp/bug3.webp", + "tests/fileformats/webp/color_cache_bits_11.webp", + "tests/fileformats/webp/dual_transform.webp", + "tests/fileformats/webp/lossless_color_transform.webp", + "tests/fileformats/webp/lossless_vec_1_0.webp", + "tests/fileformats/webp/lossless_vec_1_1.webp", + "tests/fileformats/webp/lossless_vec_1_10.webp", + "tests/fileformats/webp/lossless_vec_1_11.webp", + "tests/fileformats/webp/lossless_vec_1_12.webp", + "tests/fileformats/webp/lossless_vec_1_13.webp", + "tests/fileformats/webp/lossless_vec_1_14.webp", + "tests/fileformats/webp/lossless_vec_1_15.webp", + "tests/fileformats/webp/lossless_vec_1_2.webp", + "tests/fileformats/webp/lossless_vec_1_3.webp", + "tests/fileformats/webp/lossless_vec_1_4.webp", + "tests/fileformats/webp/lossless_vec_1_5.webp", + "tests/fileformats/webp/lossless_vec_1_6.webp", + "tests/fileformats/webp/lossless_vec_1_7.webp", + "tests/fileformats/webp/lossless_vec_1_8.webp", + "tests/fileformats/webp/lossless_vec_1_9.webp", + "tests/fileformats/webp/lossless_vec_2_0.webp", + "tests/fileformats/webp/lossless_vec_2_1.webp", + "tests/fileformats/webp/lossless_vec_2_10.webp", + "tests/fileformats/webp/lossless_vec_2_11.webp", + "tests/fileformats/webp/lossless_vec_2_12.webp", + "tests/fileformats/webp/lossless_vec_2_13.webp", + "tests/fileformats/webp/lossless_vec_2_14.webp", + "tests/fileformats/webp/lossless_vec_2_15.webp", + "tests/fileformats/webp/lossless_vec_2_2.webp", + "tests/fileformats/webp/lossless_vec_2_3.webp", + "tests/fileformats/webp/lossless_vec_2_4.webp", + "tests/fileformats/webp/lossless_vec_2_5.webp", + "tests/fileformats/webp/lossless_vec_2_6.webp", + "tests/fileformats/webp/lossless_vec_2_7.webp", + "tests/fileformats/webp/lossless_vec_2_8.webp", + "tests/fileformats/webp/lossless_vec_2_9.webp", + "tests/fileformats/webp/lossless1.webp", + "tests/fileformats/webp/lossless2.webp", + "tests/fileformats/webp/lossless3.webp", + "tests/fileformats/webp/lossless4.webp", + "tests/fileformats/webp/lossy_alpha1.webp", + "tests/fileformats/webp/lossy_alpha2.webp", + "tests/fileformats/webp/lossy_alpha3.webp", + "tests/fileformats/webp/lossy_alpha4.webp", + "tests/fileformats/webp/lossy_extreme_probabilities.webp", + "tests/fileformats/webp/lossy_q0_f100.webp", + "tests/fileformats/webp/near_lossless_75.webp", + "tests/fileformats/webp/one_color_no_palette.webp", + "tests/fileformats/webp/segment01.webp", + "tests/fileformats/webp/segment02.webp", + "tests/fileformats/webp/segment03.webp", + "tests/fileformats/webp/small_13x1.webp", + "tests/fileformats/webp/small_1x1.webp", + "tests/fileformats/webp/small_1x13.webp", + "tests/fileformats/webp/small_31x13.webp", + "tests/fileformats/webp/test.webp", + "tests/fileformats/webp/test-nostrong.webp", + "tests/fileformats/webp/very_short.webp", + "tests/fileformats/webp/vp80-00-comprehensive-001.webp", + "tests/fileformats/webp/vp80-00-comprehensive-002.webp", + "tests/fileformats/webp/vp80-00-comprehensive-003.webp", + "tests/fileformats/webp/vp80-00-comprehensive-004.webp", + "tests/fileformats/webp/vp80-00-comprehensive-005.webp", + "tests/fileformats/webp/vp80-00-comprehensive-006.webp", + "tests/fileformats/webp/vp80-00-comprehensive-007.webp", + "tests/fileformats/webp/vp80-00-comprehensive-008.webp", + "tests/fileformats/webp/vp80-00-comprehensive-009.webp", + "tests/fileformats/webp/vp80-00-comprehensive-010.webp", + "tests/fileformats/webp/vp80-00-comprehensive-011.webp", + "tests/fileformats/webp/vp80-00-comprehensive-012.webp", + "tests/fileformats/webp/vp80-00-comprehensive-013.webp", + "tests/fileformats/webp/vp80-00-comprehensive-014.webp", + "tests/fileformats/webp/vp80-00-comprehensive-015.webp", + "tests/fileformats/webp/vp80-00-comprehensive-016.webp", + "tests/fileformats/webp/vp80-00-comprehensive-017.webp", + "tests/fileformats/webp/vp80-01-intra-1400.webp", + "tests/fileformats/webp/vp80-01-intra-1411.webp", + "tests/fileformats/webp/vp80-01-intra-1416.webp", + "tests/fileformats/webp/vp80-01-intra-1417.webp", + "tests/fileformats/webp/vp80-02-inter-1402.webp", + "tests/fileformats/webp/vp80-02-inter-1412.webp", + "tests/fileformats/webp/vp80-02-inter-1418.webp", + "tests/fileformats/webp/vp80-02-inter-1424.webp", + "tests/fileformats/webp/vp80-03-segmentation-1401.webp", + "tests/fileformats/webp/vp80-03-segmentation-1403.webp", + "tests/fileformats/webp/vp80-03-segmentation-1407.webp", + "tests/fileformats/webp/vp80-03-segmentation-1408.webp", + "tests/fileformats/webp/vp80-03-segmentation-1409.webp", + "tests/fileformats/webp/vp80-03-segmentation-1410.webp", + "tests/fileformats/webp/vp80-03-segmentation-1413.webp", + "tests/fileformats/webp/vp80-03-segmentation-1414.webp", + "tests/fileformats/webp/vp80-03-segmentation-1415.webp", + "tests/fileformats/webp/vp80-03-segmentation-1425.webp", + "tests/fileformats/webp/vp80-03-segmentation-1426.webp", + "tests/fileformats/webp/vp80-03-segmentation-1427.webp", + "tests/fileformats/webp/vp80-03-segmentation-1432.webp", + "tests/fileformats/webp/vp80-03-segmentation-1435.webp", + "tests/fileformats/webp/vp80-03-segmentation-1436.webp", + "tests/fileformats/webp/vp80-03-segmentation-1437.webp", + "tests/fileformats/webp/vp80-03-segmentation-1441.webp", + "tests/fileformats/webp/vp80-03-segmentation-1442.webp", + "tests/fileformats/webp/vp80-04-partitions-1404.webp", + "tests/fileformats/webp/vp80-04-partitions-1405.webp", + "tests/fileformats/webp/vp80-04-partitions-1406.webp", + "tests/fileformats/webp/vp80-05-sharpness-1428.webp", + "tests/fileformats/webp/vp80-05-sharpness-1429.webp", + "tests/fileformats/webp/vp80-05-sharpness-1430.webp", + "tests/fileformats/webp/vp80-05-sharpness-1431.webp", + "tests/fileformats/webp/vp80-05-sharpness-1433.webp", + "tests/fileformats/webp/vp80-05-sharpness-1434.webp", + "tests/fileformats/webp/vp80-05-sharpness-1438.webp", + "tests/fileformats/webp/vp80-05-sharpness-1439.webp", + "tests/fileformats/webp/vp80-05-sharpness-1440.webp", + "tests/fileformats/webp/vp80-05-sharpness-1443.webp", +]