/// The palette (color-indexing) transform replaces pixels with indices into /// a palette that is stored before the image data. If the palette is /// sufficiently small, multiple indices are packed into a single pixel. internal static Image PaletteTransform(BitWriter b, Image image, Palette palette) { b.WriteBits(1, 1); b.WriteBits(3, 2); WritePalette(b, palette); int packSize = palette.Count <= 2 ? 8 : palette.Count <= 4 ? 4 : palette.Count <= 16 ? 2 : 1; int packedWidth = (image.Width + packSize - 1) / packSize; var palettized = new Image(packedWidth, image.Height); for (int y = 0; y < image.Height; ++y) { for (int i = 0; i < packedWidth; ++i) { int pack = 0; for (int j = 0; j < packSize; ++j) { int x = i * packSize + j; if (x >= image.Width) { break; } int colorIndex = palette.Indices[image[x, y]]; pack |= colorIndex << (j * (8 / packSize)); } palettized[i, y] = new Argb(255, 0, (byte)pack, 0); } } return(palettized); }
static void WriteHeader(BitWriter b, int width, int height, bool hasAlpha) { b.WriteBits(0x2f, 8); // signature b.WriteBits(width - 1, 14); b.WriteBits(height - 1, 14); b.WriteBits(hasAlpha ? 1 : 0, 1); b.WriteBits(0, 3); // version 0 }
/// The subtract-green transform just subtracts the value of the green /// channel from the red and blue channels, which usually decreases entropy /// in those channels. internal static Image SubtractGreenTransform(BitWriter b, Image image) { b.WriteBits(1, 1); b.WriteBits(2, 2); for (int i = 0; i < image.Pixels.Length; ++i) { var argb = image.Pixels[i]; argb.R = (byte)(argb.R - argb.G); argb.B = (byte)(argb.B - argb.G); image.Pixels[i] = argb; } return(image); }
/// The prediction transform predicts the values of pixels using their /// already decoded neighbors, storing only the difference between the /// predicted and actual value. There are 14 prediction modes and which mode /// is used is determined from an encoded subsample image. internal static Image PredictTransform(BitWriter b, Image image) { b.WriteBits(1, 1); b.WriteBits(0, 2); int tileBits = 4; int tileSize = 0; int blockedWidth = 0; int blockedHeight = 0; while (tileBits < 2 + 8) { tileSize = 1 << tileBits; blockedWidth = (image.Width + tileSize - 1) / tileSize; blockedHeight = (image.Height + tileSize - 1) / tileSize; if (blockedWidth * blockedHeight < 2000) { break; } ++tileBits; } b.WriteBits(tileBits - 2, 3); var blocks = new Image(blockedWidth, blockedHeight); var residuals = new Image(image.Width, image.Height); var accumHistos = Enumerable.Range(0, 4).Select(_ => new Histogram(256)).ToList(); for (int y = 0; y < blockedHeight; ++y) { for (int x = 0; x < blockedWidth; ++x) { int bestPrediction = 0; double bestEntropy = PredictEntropy(image, tileBits, x, y, 0, accumHistos); for (int i = 1; i < PREDICTIONS.Count; ++i) { double entropy = PredictEntropy(image, tileBits, x, y, i, accumHistos); if (entropy < bestEntropy) { bestPrediction = i; bestEntropy = entropy; } } blocks[x, y] = new Argb(255, 0, (byte)bestPrediction, 0); PredictBlock(image, residuals, tileBits, x, y, bestPrediction, accumHistos); } } ImageData.WriteImageData(b, blocks, false); return(residuals); }
/// If there are only at most two symbols in the code, it can be encoded /// efficiently. static void WriteSimpleCodeLengths(BitWriter b, List <Huffman.Code> codes) { b.WriteBits(1, 1); if (codes.Count == 0) { b.WriteBits(0, 3); return; } b.WriteBits(codes.Count - 1, 1); if (codes[0].Symbol <= 1) { b.WriteBits(0, 1); b.WriteBits(codes[0].Symbol, 1); } else { b.WriteBits(1, 1); b.WriteBits(codes[0].Symbol, 8); } if (codes.Count > 1) { b.WriteBits(codes[1].Symbol, 8); } }
/// If the size of the code is larger than two, we must transmit the length /// of every symbol in the code. To save space, the lengths are themselves /// Huffman-coded with a length code, so the lengths of the length code must /// be stored first. static void WriteNormalCodeLengths(BitWriter b, List <Huffman.Code> codes) { var encodedLengths = EncodeCodeLengths(codes); var lengthHisto = new Histogram(19); for (int i = 0; i < encodedLengths.Count; ++i) { int sym = encodedLengths[i]; lengthHisto.Hit(sym); if (sym >= 16) { i += 1; } } var lengthCodes = Huffman.BuildCodes(lengthHisto, 7); int lengthCodeCount = 0; for (int i = 0; i < 19; ++i) { if (lengthHisto[CODE_LENGTH_ORDER[i]] > 0) { lengthCodeCount = i + 1; } } if (lengthCodeCount < 4) { lengthCodeCount = 4; } b.WriteBits(0, 1); b.WriteBits(lengthCodeCount - 4, 4); for (int i = 0; i < lengthCodeCount; ++i) { b.WriteBits(lengthCodes[CODE_LENGTH_ORDER[i]].Length, 3); } b.WriteBits(0, 1); for (int i = 0; i < encodedLengths.Count; ++i) { int sym = encodedLengths[i]; b.WriteCode(lengthCodes[sym]); if (sym == 16) { b.WriteBits(encodedLengths[++i], 2); } else if (sym == 17) { b.WriteBits(encodedLengths[++i], 3); } else if (sym == 18) { b.WriteBits(encodedLengths[++i], 7); } } }
/// The palette is encoded as an image with height 1. static void WritePalette(BitWriter b, Palette palette) { var image = new Image(palette.Count, 1); for (int i = 0; i < palette.Count; ++i) { image.Pixels[i] = i == 0 ? palette.Colors[0] : palette.Colors[i] - palette.Colors[i - 1]; } b.WriteBits(palette.Count - 1, 8); ImageData.WriteImageData(b, image, false); }
/// Encodes the image as a raw VP8L bitstream. static void WriteImageBitstream(BitWriter b, Image image) { var analysis = Analysis.AnalyzeImage(image); WriteHeader(b, image.Width, image.Height, analysis.HasAlpha); if (analysis.PaletteOrNull != null) { image = Transform.PaletteTransform(b, image, analysis.PaletteOrNull); } if (analysis.UseSubtractGreen) { image = Transform.SubtractGreenTransform(b, image); } if (analysis.UsePredict) { image = Transform.PredictTransform(b, image); } b.WriteBits(0, 1); ImageData.WriteImageData(b, image, true, analysis.ColorCacheBits); }
/// Encodes the image into entropy-coded bitstream. The isRecursive flag /// must be true iff the data represents the main ARGB image. internal static void WriteImageData(BitWriter b, Image image, bool isRecursive, int colorCacheBits = 0) { if (colorCacheBits > 0) { if (colorCacheBits > 11) { throw new ArgumentException("Too many color cache bits"); } b.WriteBits(1, 1); b.WriteBits(colorCacheBits, 4); } else { b.WriteBits(0, 1); } if (isRecursive) { b.WriteBits(0, 1); // no meta-Huffman image } var encoded = EncodeImageData(image, colorCacheBits); var histos = new List <Histogram> { new Histogram(256 + 24 + (colorCacheBits > 0 ? (1 << colorCacheBits) : 0)), new Histogram(256), new Histogram(256), new Histogram(256), new Histogram(40), }; for (int i = 0; i < encoded.Count; ++i) { histos[0].Hit(encoded[i]); if (encoded[i] < 256) { histos[1].Hit(encoded[i + 1]); histos[2].Hit(encoded[i + 2]); histos[3].Hit(encoded[i + 3]); i += 3; } else if (encoded[i] < 256 + 24) { histos[4].Hit(encoded[i + 2]); i += 3; } } var codess = new List <List <Huffman.Code> >(); for (int i = 0; i < 5; ++i) { var codes = Huffman.BuildCodes(histos[i], 16); CodeLengths.WriteCodeLengths(b, codes); codess.Add(codes); } for (int i = 0; i < encoded.Count; ++i) { b.WriteCode(codess[0][encoded[i]]); if (encoded[i] < 256) { b.WriteCode(codess[1][encoded[i + 1]]); b.WriteCode(codess[2][encoded[i + 2]]); b.WriteCode(codess[3][encoded[i + 3]]); i += 3; } else if (encoded[i] < 256 + 24) { b.WriteBits(encoded[i + 1], ExtraBitsCount(encoded[i] - 256)); b.WriteCode(codess[4][encoded[i + 2]]); b.WriteBits(encoded[i + 3], ExtraBitsCount(encoded[i + 2])); i += 3; } } }