diff --git a/ImageSharp.sln b/ImageSharp.sln
index f1d4afef41..8fc463c720 100644
--- a/ImageSharp.sln
+++ b/ImageSharp.sln
@@ -332,6 +332,165 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SharedInfrastructure", "sha
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests.ProfilingSandbox", "tests\ImageSharp.Tests.ProfilingSandbox\ImageSharp.Tests.ProfilingSandbox.csproj", "{FC527290-2F22-432C-B77B-6E815726B02C}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webp", "Webp", "{983A31E2-5E26-4058-BD6E-03B4922D4BBF}"
+ ProjectSection(SolutionItems) = preProject
+ tests\Images\Input\WebP\alpha_color_cache.webp = tests\Images\Input\WebP\alpha_color_cache.webp
+ tests\Images\Input\WebP\alpha_filter_0_method_0.webp = tests\Images\Input\WebP\alpha_filter_0_method_0.webp
+ tests\Images\Input\WebP\alpha_filter_0_method_1.webp = tests\Images\Input\WebP\alpha_filter_0_method_1.webp
+ tests\Images\Input\WebP\alpha_filter_1.webp = tests\Images\Input\WebP\alpha_filter_1.webp
+ tests\Images\Input\WebP\alpha_filter_1_method_0.webp = tests\Images\Input\WebP\alpha_filter_1_method_0.webp
+ tests\Images\Input\WebP\alpha_filter_1_method_1.webp = tests\Images\Input\WebP\alpha_filter_1_method_1.webp
+ tests\Images\Input\WebP\alpha_filter_2.webp = tests\Images\Input\WebP\alpha_filter_2.webp
+ tests\Images\Input\WebP\alpha_filter_2_method_0.webp = tests\Images\Input\WebP\alpha_filter_2_method_0.webp
+ tests\Images\Input\WebP\alpha_filter_2_method_1.webp = tests\Images\Input\WebP\alpha_filter_2_method_1.webp
+ tests\Images\Input\WebP\alpha_filter_3.webp = tests\Images\Input\WebP\alpha_filter_3.webp
+ tests\Images\Input\WebP\alpha_filter_3_method_0.webp = tests\Images\Input\WebP\alpha_filter_3_method_0.webp
+ tests\Images\Input\WebP\alpha_filter_3_method_1.webp = tests\Images\Input\WebP\alpha_filter_3_method_1.webp
+ tests\Images\Input\WebP\alpha_no_compression.webp = tests\Images\Input\WebP\alpha_no_compression.webp
+ tests\Images\Input\WebP\animated-webp.webp = tests\Images\Input\WebP\animated-webp.webp
+ tests\Images\Input\WebP\animated2.webp = tests\Images\Input\WebP\animated2.webp
+ tests\Images\Input\WebP\animated3.webp = tests\Images\Input\WebP\animated3.webp
+ tests\Images\Input\WebP\animated_lossy.webp = tests\Images\Input\WebP\animated_lossy.webp
+ tests\Images\Input\WebP\bad_palette_index.webp = tests\Images\Input\WebP\bad_palette_index.webp
+ tests\Images\Input\WebP\big_endian_bug_393.webp = tests\Images\Input\WebP\big_endian_bug_393.webp
+ tests\Images\Input\WebP\bryce.webp = tests\Images\Input\WebP\bryce.webp
+ tests\Images\Input\WebP\bug3.webp = tests\Images\Input\WebP\bug3.webp
+ tests\Images\Input\WebP\color_cache_bits_11.webp = tests\Images\Input\WebP\color_cache_bits_11.webp
+ tests\Images\Input\WebP\grid.bmp = tests\Images\Input\WebP\grid.bmp
+ tests\Images\Input\WebP\grid.pam = tests\Images\Input\WebP\grid.pam
+ tests\Images\Input\WebP\grid.pgm = tests\Images\Input\WebP\grid.pgm
+ tests\Images\Input\WebP\grid.png = tests\Images\Input\WebP\grid.png
+ tests\Images\Input\WebP\grid.ppm = tests\Images\Input\WebP\grid.ppm
+ tests\Images\Input\WebP\grid.tiff = tests\Images\Input\WebP\grid.tiff
+ tests\Images\Input\WebP\libwebp_tests.md5 = tests\Images\Input\WebP\libwebp_tests.md5
+ tests\Images\Input\WebP\lossless1.webp = tests\Images\Input\WebP\lossless1.webp
+ tests\Images\Input\WebP\lossless2.webp = tests\Images\Input\WebP\lossless2.webp
+ tests\Images\Input\WebP\lossless3.webp = tests\Images\Input\WebP\lossless3.webp
+ tests\Images\Input\WebP\lossless4.webp = tests\Images\Input\WebP\lossless4.webp
+ tests\Images\Input\WebP\lossless_big_random_alpha.webp = tests\Images\Input\WebP\lossless_big_random_alpha.webp
+ tests\Images\Input\WebP\lossless_color_transform.bmp = tests\Images\Input\WebP\lossless_color_transform.bmp
+ tests\Images\Input\WebP\lossless_color_transform.pam = tests\Images\Input\WebP\lossless_color_transform.pam
+ tests\Images\Input\WebP\lossless_color_transform.pgm = tests\Images\Input\WebP\lossless_color_transform.pgm
+ tests\Images\Input\WebP\lossless_color_transform.ppm = tests\Images\Input\WebP\lossless_color_transform.ppm
+ tests\Images\Input\WebP\lossless_color_transform.tiff = tests\Images\Input\WebP\lossless_color_transform.tiff
+ tests\Images\Input\WebP\lossless_color_transform.webp = tests\Images\Input\WebP\lossless_color_transform.webp
+ tests\Images\Input\WebP\lossless_vec_1_0.webp = tests\Images\Input\WebP\lossless_vec_1_0.webp
+ tests\Images\Input\WebP\lossless_vec_1_1.webp = tests\Images\Input\WebP\lossless_vec_1_1.webp
+ tests\Images\Input\WebP\lossless_vec_1_10.webp = tests\Images\Input\WebP\lossless_vec_1_10.webp
+ tests\Images\Input\WebP\lossless_vec_1_11.webp = tests\Images\Input\WebP\lossless_vec_1_11.webp
+ tests\Images\Input\WebP\lossless_vec_1_12.webp = tests\Images\Input\WebP\lossless_vec_1_12.webp
+ tests\Images\Input\WebP\lossless_vec_1_13.webp = tests\Images\Input\WebP\lossless_vec_1_13.webp
+ tests\Images\Input\WebP\lossless_vec_1_14.webp = tests\Images\Input\WebP\lossless_vec_1_14.webp
+ tests\Images\Input\WebP\lossless_vec_1_15.webp = tests\Images\Input\WebP\lossless_vec_1_15.webp
+ tests\Images\Input\WebP\lossless_vec_1_2.webp = tests\Images\Input\WebP\lossless_vec_1_2.webp
+ tests\Images\Input\WebP\lossless_vec_1_3.webp = tests\Images\Input\WebP\lossless_vec_1_3.webp
+ tests\Images\Input\WebP\lossless_vec_1_4.webp = tests\Images\Input\WebP\lossless_vec_1_4.webp
+ tests\Images\Input\WebP\lossless_vec_1_5.webp = tests\Images\Input\WebP\lossless_vec_1_5.webp
+ tests\Images\Input\WebP\lossless_vec_1_6.webp = tests\Images\Input\WebP\lossless_vec_1_6.webp
+ tests\Images\Input\WebP\lossless_vec_1_7.webp = tests\Images\Input\WebP\lossless_vec_1_7.webp
+ tests\Images\Input\WebP\lossless_vec_1_8.webp = tests\Images\Input\WebP\lossless_vec_1_8.webp
+ tests\Images\Input\WebP\lossless_vec_1_9.webp = tests\Images\Input\WebP\lossless_vec_1_9.webp
+ tests\Images\Input\WebP\lossless_vec_2_0.webp = tests\Images\Input\WebP\lossless_vec_2_0.webp
+ tests\Images\Input\WebP\lossless_vec_2_1.webp = tests\Images\Input\WebP\lossless_vec_2_1.webp
+ tests\Images\Input\WebP\lossless_vec_2_10.webp = tests\Images\Input\WebP\lossless_vec_2_10.webp
+ tests\Images\Input\WebP\lossless_vec_2_11.webp = tests\Images\Input\WebP\lossless_vec_2_11.webp
+ tests\Images\Input\WebP\lossless_vec_2_12.webp = tests\Images\Input\WebP\lossless_vec_2_12.webp
+ tests\Images\Input\WebP\lossless_vec_2_13.webp = tests\Images\Input\WebP\lossless_vec_2_13.webp
+ tests\Images\Input\WebP\lossless_vec_2_14.webp = tests\Images\Input\WebP\lossless_vec_2_14.webp
+ tests\Images\Input\WebP\lossless_vec_2_15.webp = tests\Images\Input\WebP\lossless_vec_2_15.webp
+ tests\Images\Input\WebP\lossless_vec_2_2.webp = tests\Images\Input\WebP\lossless_vec_2_2.webp
+ tests\Images\Input\WebP\lossless_vec_2_3.webp = tests\Images\Input\WebP\lossless_vec_2_3.webp
+ tests\Images\Input\WebP\lossless_vec_2_4.webp = tests\Images\Input\WebP\lossless_vec_2_4.webp
+ tests\Images\Input\WebP\lossless_vec_2_5.webp = tests\Images\Input\WebP\lossless_vec_2_5.webp
+ tests\Images\Input\WebP\lossless_vec_2_6.webp = tests\Images\Input\WebP\lossless_vec_2_6.webp
+ tests\Images\Input\WebP\lossless_vec_2_7.webp = tests\Images\Input\WebP\lossless_vec_2_7.webp
+ tests\Images\Input\WebP\lossless_vec_2_8.webp = tests\Images\Input\WebP\lossless_vec_2_8.webp
+ tests\Images\Input\WebP\lossless_vec_2_9.webp = tests\Images\Input\WebP\lossless_vec_2_9.webp
+ tests\Images\Input\WebP\lossless_vec_list.txt = tests\Images\Input\WebP\lossless_vec_list.txt
+ tests\Images\Input\WebP\lossy_alpha1.webp = tests\Images\Input\WebP\lossy_alpha1.webp
+ tests\Images\Input\WebP\lossy_alpha2.webp = tests\Images\Input\WebP\lossy_alpha2.webp
+ tests\Images\Input\WebP\lossy_alpha3.webp = tests\Images\Input\WebP\lossy_alpha3.webp
+ tests\Images\Input\WebP\lossy_alpha4.webp = tests\Images\Input\WebP\lossy_alpha4.webp
+ tests\Images\Input\WebP\lossy_extreme_probabilities.webp = tests\Images\Input\WebP\lossy_extreme_probabilities.webp
+ tests\Images\Input\WebP\lossy_q0_f100.webp = tests\Images\Input\WebP\lossy_q0_f100.webp
+ tests\Images\Input\WebP\near_lossless_75.webp = tests\Images\Input\WebP\near_lossless_75.webp
+ tests\Images\Input\WebP\peak.bmp = tests\Images\Input\WebP\peak.bmp
+ tests\Images\Input\WebP\peak.pam = tests\Images\Input\WebP\peak.pam
+ tests\Images\Input\WebP\peak.pgm = tests\Images\Input\WebP\peak.pgm
+ tests\Images\Input\WebP\peak.png = tests\Images\Input\WebP\peak.png
+ tests\Images\Input\WebP\peak.ppm = tests\Images\Input\WebP\peak.ppm
+ tests\Images\Input\WebP\peak.tiff = tests\Images\Input\WebP\peak.tiff
+ tests\Images\Input\WebP\segment01.webp = tests\Images\Input\WebP\segment01.webp
+ tests\Images\Input\WebP\segment02.webp = tests\Images\Input\WebP\segment02.webp
+ tests\Images\Input\WebP\segment03.webp = tests\Images\Input\WebP\segment03.webp
+ tests\Images\Input\WebP\small_13x1.webp = tests\Images\Input\WebP\small_13x1.webp
+ tests\Images\Input\WebP\small_1x1.webp = tests\Images\Input\WebP\small_1x1.webp
+ tests\Images\Input\WebP\small_1x13.webp = tests\Images\Input\WebP\small_1x13.webp
+ tests\Images\Input\WebP\small_31x13.webp = tests\Images\Input\WebP\small_31x13.webp
+ tests\Images\Input\WebP\test-nostrong.webp = tests\Images\Input\WebP\test-nostrong.webp
+ tests\Images\Input\WebP\test.webp = tests\Images\Input\WebP\test.webp
+ tests\Images\Input\WebP\test_cwebp.sh = tests\Images\Input\WebP\test_cwebp.sh
+ tests\Images\Input\WebP\test_dwebp.sh = tests\Images\Input\WebP\test_dwebp.sh
+ tests\Images\Input\WebP\test_lossless.sh = tests\Images\Input\WebP\test_lossless.sh
+ tests\Images\Input\WebP\very_short.webp = tests\Images\Input\WebP\very_short.webp
+ tests\Images\Input\WebP\vp80-00-comprehensive-001.webp = tests\Images\Input\WebP\vp80-00-comprehensive-001.webp
+ tests\Images\Input\WebP\vp80-00-comprehensive-002.webp = tests\Images\Input\WebP\vp80-00-comprehensive-002.webp
+ tests\Images\Input\WebP\vp80-00-comprehensive-003.webp = tests\Images\Input\WebP\vp80-00-comprehensive-003.webp
+ tests\Images\Input\WebP\vp80-00-comprehensive-004.webp = tests\Images\Input\WebP\vp80-00-comprehensive-004.webp
+ tests\Images\Input\WebP\vp80-00-comprehensive-005.webp = tests\Images\Input\WebP\vp80-00-comprehensive-005.webp
+ tests\Images\Input\WebP\vp80-00-comprehensive-006.webp = tests\Images\Input\WebP\vp80-00-comprehensive-006.webp
+ tests\Images\Input\WebP\vp80-00-comprehensive-007.webp = tests\Images\Input\WebP\vp80-00-comprehensive-007.webp
+ tests\Images\Input\WebP\vp80-00-comprehensive-008.webp = tests\Images\Input\WebP\vp80-00-comprehensive-008.webp
+ tests\Images\Input\WebP\vp80-00-comprehensive-009.webp = tests\Images\Input\WebP\vp80-00-comprehensive-009.webp
+ tests\Images\Input\WebP\vp80-00-comprehensive-010.webp = tests\Images\Input\WebP\vp80-00-comprehensive-010.webp
+ tests\Images\Input\WebP\vp80-00-comprehensive-011.webp = tests\Images\Input\WebP\vp80-00-comprehensive-011.webp
+ tests\Images\Input\WebP\vp80-00-comprehensive-012.webp = tests\Images\Input\WebP\vp80-00-comprehensive-012.webp
+ tests\Images\Input\WebP\vp80-00-comprehensive-013.webp = tests\Images\Input\WebP\vp80-00-comprehensive-013.webp
+ tests\Images\Input\WebP\vp80-00-comprehensive-014.webp = tests\Images\Input\WebP\vp80-00-comprehensive-014.webp
+ tests\Images\Input\WebP\vp80-00-comprehensive-015.webp = tests\Images\Input\WebP\vp80-00-comprehensive-015.webp
+ tests\Images\Input\WebP\vp80-00-comprehensive-016.webp = tests\Images\Input\WebP\vp80-00-comprehensive-016.webp
+ tests\Images\Input\WebP\vp80-00-comprehensive-017.webp = tests\Images\Input\WebP\vp80-00-comprehensive-017.webp
+ tests\Images\Input\WebP\vp80-01-intra-1400.webp = tests\Images\Input\WebP\vp80-01-intra-1400.webp
+ tests\Images\Input\WebP\vp80-01-intra-1411.webp = tests\Images\Input\WebP\vp80-01-intra-1411.webp
+ tests\Images\Input\WebP\vp80-01-intra-1416.webp = tests\Images\Input\WebP\vp80-01-intra-1416.webp
+ tests\Images\Input\WebP\vp80-01-intra-1417.webp = tests\Images\Input\WebP\vp80-01-intra-1417.webp
+ tests\Images\Input\WebP\vp80-02-inter-1402.webp = tests\Images\Input\WebP\vp80-02-inter-1402.webp
+ tests\Images\Input\WebP\vp80-02-inter-1412.webp = tests\Images\Input\WebP\vp80-02-inter-1412.webp
+ tests\Images\Input\WebP\vp80-02-inter-1418.webp = tests\Images\Input\WebP\vp80-02-inter-1418.webp
+ tests\Images\Input\WebP\vp80-02-inter-1424.webp = tests\Images\Input\WebP\vp80-02-inter-1424.webp
+ tests\Images\Input\WebP\vp80-03-segmentation-1401.webp = tests\Images\Input\WebP\vp80-03-segmentation-1401.webp
+ tests\Images\Input\WebP\vp80-03-segmentation-1403.webp = tests\Images\Input\WebP\vp80-03-segmentation-1403.webp
+ tests\Images\Input\WebP\vp80-03-segmentation-1407.webp = tests\Images\Input\WebP\vp80-03-segmentation-1407.webp
+ tests\Images\Input\WebP\vp80-03-segmentation-1408.webp = tests\Images\Input\WebP\vp80-03-segmentation-1408.webp
+ tests\Images\Input\WebP\vp80-03-segmentation-1409.webp = tests\Images\Input\WebP\vp80-03-segmentation-1409.webp
+ tests\Images\Input\WebP\vp80-03-segmentation-1410.webp = tests\Images\Input\WebP\vp80-03-segmentation-1410.webp
+ tests\Images\Input\WebP\vp80-03-segmentation-1413.webp = tests\Images\Input\WebP\vp80-03-segmentation-1413.webp
+ tests\Images\Input\WebP\vp80-03-segmentation-1414.webp = tests\Images\Input\WebP\vp80-03-segmentation-1414.webp
+ tests\Images\Input\WebP\vp80-03-segmentation-1415.webp = tests\Images\Input\WebP\vp80-03-segmentation-1415.webp
+ tests\Images\Input\WebP\vp80-03-segmentation-1425.webp = tests\Images\Input\WebP\vp80-03-segmentation-1425.webp
+ tests\Images\Input\WebP\vp80-03-segmentation-1426.webp = tests\Images\Input\WebP\vp80-03-segmentation-1426.webp
+ tests\Images\Input\WebP\vp80-03-segmentation-1427.webp = tests\Images\Input\WebP\vp80-03-segmentation-1427.webp
+ tests\Images\Input\WebP\vp80-03-segmentation-1432.webp = tests\Images\Input\WebP\vp80-03-segmentation-1432.webp
+ tests\Images\Input\WebP\vp80-03-segmentation-1435.webp = tests\Images\Input\WebP\vp80-03-segmentation-1435.webp
+ tests\Images\Input\WebP\vp80-03-segmentation-1436.webp = tests\Images\Input\WebP\vp80-03-segmentation-1436.webp
+ tests\Images\Input\WebP\vp80-03-segmentation-1437.webp = tests\Images\Input\WebP\vp80-03-segmentation-1437.webp
+ tests\Images\Input\WebP\vp80-03-segmentation-1441.webp = tests\Images\Input\WebP\vp80-03-segmentation-1441.webp
+ tests\Images\Input\WebP\vp80-03-segmentation-1442.webp = tests\Images\Input\WebP\vp80-03-segmentation-1442.webp
+ tests\Images\Input\WebP\vp80-04-partitions-1404.webp = tests\Images\Input\WebP\vp80-04-partitions-1404.webp
+ tests\Images\Input\WebP\vp80-04-partitions-1405.webp = tests\Images\Input\WebP\vp80-04-partitions-1405.webp
+ tests\Images\Input\WebP\vp80-04-partitions-1406.webp = tests\Images\Input\WebP\vp80-04-partitions-1406.webp
+ tests\Images\Input\WebP\vp80-05-sharpness-1428.webp = tests\Images\Input\WebP\vp80-05-sharpness-1428.webp
+ tests\Images\Input\WebP\vp80-05-sharpness-1429.webp = tests\Images\Input\WebP\vp80-05-sharpness-1429.webp
+ tests\Images\Input\WebP\vp80-05-sharpness-1430.webp = tests\Images\Input\WebP\vp80-05-sharpness-1430.webp
+ tests\Images\Input\WebP\vp80-05-sharpness-1431.webp = tests\Images\Input\WebP\vp80-05-sharpness-1431.webp
+ tests\Images\Input\WebP\vp80-05-sharpness-1433.webp = tests\Images\Input\WebP\vp80-05-sharpness-1433.webp
+ tests\Images\Input\WebP\vp80-05-sharpness-1434.webp = tests\Images\Input\WebP\vp80-05-sharpness-1434.webp
+ tests\Images\Input\WebP\vp80-05-sharpness-1438.webp = tests\Images\Input\WebP\vp80-05-sharpness-1438.webp
+ tests\Images\Input\WebP\vp80-05-sharpness-1439.webp = tests\Images\Input\WebP\vp80-05-sharpness-1439.webp
+ tests\Images\Input\WebP\vp80-05-sharpness-1440.webp = tests\Images\Input\WebP\vp80-05-sharpness-1440.webp
+ tests\Images\Input\WebP\vp80-05-sharpness-1443.webp = tests\Images\Input\WebP\vp80-05-sharpness-1443.webp
+ EndProjectSection
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{2aa31a1f-142c-43f4-8687-09abca4b3a26}*SharedItemsImports = 5
@@ -420,6 +579,7 @@ Global
{C0D7754B-5277-438E-ABEB-2BA34401B5A7} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D}
{68A8CC40-6AED-4E96-B524-31B1158FDEEA} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
{FC527290-2F22-432C-B77B-6E815726B02C} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
+ {983A31E2-5E26-4058-BD6E-03B4922D4BBF} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795}
diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs
index 47c7c54ea0..15a3ed40fd 100644
--- a/src/ImageSharp/Configuration.cs
+++ b/src/ImageSharp/Configuration.cs
@@ -9,6 +9,7 @@
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
+using SixLabors.ImageSharp.Formats.WebP;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Processing;
@@ -162,7 +163,8 @@ internal static Configuration CreateDefaultInstance()
new JpegConfigurationModule(),
new GifConfigurationModule(),
new BmpConfigurationModule(),
- new TgaConfigurationModule());
+ new TgaConfigurationModule(),
+ new WebPConfigurationModule());
}
}
}
diff --git a/src/ImageSharp/Formats/WebP/AlphaDecoder.cs b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs
new file mode 100644
index 0000000000..5d0ba360a7
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/AlphaDecoder.cs
@@ -0,0 +1,309 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Implements decoding for lossy alpha chunks which may be compressed.
+ ///
+ internal class AlphaDecoder : IDisposable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The width of the image.
+ /// The height of the image.
+ /// The (maybe compressed) alpha data.
+ /// The first byte of the alpha image stream contains information on ow to decode the stream.
+ /// Used for allocating memory during decoding.
+ /// The configuration.
+ public AlphaDecoder(int width, int height, byte[] data, byte alphaChunkHeader, MemoryAllocator memoryAllocator, Configuration configuration)
+ {
+ this.Width = width;
+ this.Height = height;
+ this.Data = data;
+ this.LastRow = 0;
+
+ // Compression method: Either 0 (no compression) or 1 (Compressed using the WebP lossless format)
+ int method = alphaChunkHeader & 0x03;
+ if (method < 0 || method > 1)
+ {
+ WebPThrowHelper.ThrowImageFormatException($"unexpected alpha compression method {method} found");
+ }
+
+ this.Compressed = !(method is 0);
+
+ // The filtering method used. Only values between 0 and 3 are valid.
+ int filter = (alphaChunkHeader >> 2) & 0x03;
+ if (filter < 0 || filter > 3)
+ {
+ WebPThrowHelper.ThrowImageFormatException($"unexpected alpha filter method {filter} found");
+ }
+
+ this.AlphaFilterType = (WebPAlphaFilterType)filter;
+
+ // These INFORMATIVE bits are used to signal the pre-processing that has been performed during compression.
+ // The decoder can use this information to e.g. dither the values or smooth the gradients prior to display.
+ // 0: no pre-processing, 1: level reduction
+ this.PreProcessing = (alphaChunkHeader >> 4) & 0x03;
+
+ this.Vp8LDec = new Vp8LDecoder(width, height, memoryAllocator);
+
+ this.Alpha = memoryAllocator.Allocate(width * height);
+
+ if (this.Compressed)
+ {
+ var bitReader = new Vp8LBitReader(data);
+ this.LosslessDecoder = new WebPLosslessDecoder(bitReader, memoryAllocator, configuration);
+ this.LosslessDecoder.DecodeImageStream(this.Vp8LDec, width, height, true);
+ }
+ }
+
+ ///
+ /// Gets the the width of the image.
+ ///
+ public int Width { get; }
+
+ ///
+ /// Gets the height of the image.
+ ///
+ public int Height { get; }
+
+ ///
+ /// Gets the used filter type.
+ ///
+ public WebPAlphaFilterType AlphaFilterType { get; }
+
+ ///
+ /// Gets or sets the last decoded row.
+ ///
+ public int LastRow { get; set; }
+
+ ///
+ /// Gets or sets the row before the last decoded row.
+ ///
+ public int PrevRow { get; set; }
+
+ ///
+ /// Gets information for decoding Vp8L compressed alpha data.
+ ///
+ public Vp8LDecoder Vp8LDec { get; }
+
+ ///
+ /// Gets the decoded alpha data.
+ ///
+ public IMemoryOwner Alpha { get; }
+
+ public int CropTop { get; }
+
+ ///
+ /// Gets a value indicating whether pre-processing was used during compression.
+ /// 0: no pre-processing, 1: level reduction.
+ ///
+ private int PreProcessing { get; }
+
+ ///
+ /// Gets a value indicating whether the alpha channel uses compression.
+ ///
+ private bool Compressed { get; }
+
+ ///
+ /// Gets the (maybe compressed) alpha data.
+ ///
+ private byte[] Data { get; }
+
+ ///
+ /// Gets the Vp8L decoder which is used to de compress the alpha channel, if needed.
+ ///
+ private WebPLosslessDecoder LosslessDecoder { get; }
+
+ ///
+ /// Gets or sets a value indicating whether the decoding needs 1 byte per pixel for decoding.
+ /// Although Alpha Channel requires only 1 byte per pixel, sometimes Vp8LDecoder may need to allocate
+ /// 4 bytes per pixel internally during decode.
+ ///
+ public bool Use8BDecode { get; set; }
+
+ ///
+ /// Decodes and filters the maybe compressed alpha data.
+ ///
+ public void Decode()
+ {
+ if (this.Compressed is false)
+ {
+ if (this.Data.Length < (this.Width * this.Height))
+ {
+ WebPThrowHelper.ThrowImageFormatException("not enough data in the ALPH chunk");
+ }
+
+ Span alphaSpan = this.Alpha.Memory.Span;
+ if (this.AlphaFilterType == WebPAlphaFilterType.None)
+ {
+ this.Data.AsSpan(0, this.Width * this.Height).CopyTo(alphaSpan);
+ return;
+ }
+
+ Span deltas = this.Data.AsSpan();
+ Span dst = alphaSpan;
+ Span prev = null;
+ for (int y = 0; y < this.Height; ++y)
+ {
+ switch (this.AlphaFilterType)
+ {
+ case WebPAlphaFilterType.Horizontal:
+ HorizontalUnfilter(prev, deltas, dst, this.Width);
+ break;
+ case WebPAlphaFilterType.Vertical:
+ VerticalUnfilter(prev, deltas, dst, this.Width);
+ break;
+ case WebPAlphaFilterType.Gradient:
+ GradientUnfilter(prev, deltas, dst, this.Width);
+ break;
+ }
+
+ prev = dst;
+ deltas = deltas.Slice(this.Width);
+ dst = dst.Slice(this.Width);
+ }
+ }
+ else
+ {
+ this.LosslessDecoder.DecodeAlphaData(this);
+ }
+ }
+
+ ///
+ /// Applies filtering to a set of rows.
+ ///
+ /// The first row index to start filtering.
+ /// The last row index for filtering.
+ /// The destination to store the filtered data.
+ /// The stride to use.
+ public void AlphaApplyFilter(int firstRow, int lastRow, Span dst, int stride)
+ {
+ if (this.AlphaFilterType is WebPAlphaFilterType.None)
+ {
+ return;
+ }
+
+ Span alphaSpan = this.Alpha.Memory.Span;
+ Span prev = this.PrevRow == 0 ? null : alphaSpan.Slice(this.Width * this.PrevRow);
+ for (int y = firstRow; y < lastRow; ++y)
+ {
+ switch (this.AlphaFilterType)
+ {
+ case WebPAlphaFilterType.Horizontal:
+ HorizontalUnfilter(prev, dst, dst, this.Width);
+ break;
+ case WebPAlphaFilterType.Vertical:
+ VerticalUnfilter(prev, dst, dst, this.Width);
+ break;
+ case WebPAlphaFilterType.Gradient:
+ GradientUnfilter(prev, dst, dst, this.Width);
+ break;
+ }
+
+ prev = dst;
+ dst = dst.Slice(stride);
+ }
+
+ this.PrevRow = lastRow - 1;
+ }
+
+ private static void HorizontalUnfilter(Span prev, Span input, Span dst, int width)
+ {
+ byte pred = (byte)(prev == null ? 0 : prev[0]);
+
+ for (int i = 0; i < width; ++i)
+ {
+ dst[i] = (byte)(pred + input[i]);
+ pred = dst[i];
+ }
+ }
+
+ private static void VerticalUnfilter(Span prev, Span input, Span dst, int width)
+ {
+ if (prev == null)
+ {
+ HorizontalUnfilter(null, input, dst, width);
+ }
+ else
+ {
+ for (int i = 0; i < width; ++i)
+ {
+ dst[i] = (byte)(prev[i] + input[i]);
+ }
+ }
+ }
+
+ private static void GradientUnfilter(Span prev, Span input, Span dst, int width)
+ {
+ if (prev == null)
+ {
+ HorizontalUnfilter(null, input, dst, width);
+ }
+ else
+ {
+ byte top = prev[0];
+ byte topLeft = top;
+ byte left = top;
+ for (int i = 0; i < width; ++i)
+ {
+ top = prev[i];
+ left = (byte)(input[i] + GradientPredictor(left, top, topLeft));
+ topLeft = top;
+ dst[i] = left;
+ }
+ }
+ }
+
+ private static bool Is8bOptimizable(Vp8LMetadata hdr)
+ {
+ if (hdr.ColorCacheSize > 0)
+ {
+ return false;
+ }
+
+ // When the Huffman tree contains only one symbol, we can skip the
+ // call to ReadSymbol() for red/blue/alpha channels.
+ for (int i = 0; i < hdr.NumHTreeGroups; ++i)
+ {
+ List htrees = hdr.HTreeGroups[i].HTrees;
+ if (htrees[HuffIndex.Red][0].Value > 0)
+ {
+ return false;
+ }
+
+ if (htrees[HuffIndex.Blue][0].Value > 0)
+ {
+ return false;
+ }
+
+ if (htrees[HuffIndex.Alpha][0].Value > 0)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static int GradientPredictor(byte a, byte b, byte c)
+ {
+ int g = a + b - c;
+ return ((g & ~0xff) is 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit
+ }
+
+ ///
+ public void Dispose()
+ {
+ this.Vp8LDec?.Dispose();
+ this.Alpha?.Dispose();
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/BitReaderBase.cs b/src/ImageSharp/Formats/WebP/BitReaderBase.cs
new file mode 100644
index 0000000000..3a5bf4f4ae
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/BitReaderBase.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.IO;
+
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Base class for VP8 and VP8L bitreader.
+ ///
+ internal abstract class BitReaderBase
+ {
+ ///
+ /// Gets or sets the raw encoded image data.
+ ///
+ public byte[] Data { get; set; }
+
+ ///
+ /// Copies the raw encoded image data from the stream into a byte array.
+ ///
+ /// The input stream.
+ /// Number of bytes to read as indicated from the chunk size.
+ /// Used for allocating memory during reading data from the stream.
+ protected void ReadImageDataFromStream(Stream input, int bytesToRead, MemoryAllocator memoryAllocator)
+ {
+ using (var ms = new MemoryStream())
+ using (IManagedByteBuffer buffer = memoryAllocator.AllocateManagedByteBuffer(4096))
+ {
+ Span bufferSpan = buffer.GetSpan();
+ int read;
+ while (bytesToRead > 0 && (read = input.Read(buffer.Array, 0, Math.Min(bufferSpan.Length, bytesToRead))) > 0)
+ {
+ ms.Write(buffer.Array, 0, read);
+ bytesToRead -= read;
+ }
+
+ if (bytesToRead > 0)
+ {
+ WebPThrowHelper.ThrowImageFormatException("image file has insufficient data");
+ }
+
+ this.Data = ms.ToArray();
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/ColorCache.cs b/src/ImageSharp/Formats/WebP/ColorCache.cs
new file mode 100644
index 0000000000..d5834a4c80
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/ColorCache.cs
@@ -0,0 +1,60 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// A small hash-addressed array to store recently used colors, to be able to recall them with shorter codes.
+ ///
+ internal class ColorCache
+ {
+ private const uint HashMul = 0x1e35a7bdu;
+
+ ///
+ /// Gets the color entries.
+ ///
+ public uint[] Colors { get; private set; }
+
+ ///
+ /// Gets the hash shift: 32 - hashBits.
+ ///
+ public int HashShift { get; private set; }
+
+ ///
+ /// Gets the hash bits.
+ ///
+ public int HashBits { get; private set; }
+
+ ///
+ /// Initializes a new color cache.
+ ///
+ /// The hashBits determine the size of cache. It will be 1 left shifted by hashBits.
+ public void Init(int hashBits)
+ {
+ int hashSize = 1 << hashBits;
+ this.Colors = new uint[hashSize];
+ this.HashBits = hashBits;
+ this.HashShift = 32 - hashBits;
+ }
+
+ ///
+ /// Inserts a new color into the cache.
+ ///
+ /// The color to insert.
+ public void Insert(uint argb)
+ {
+ int key = this.HashPix(argb, this.HashShift);
+ this.Colors[key] = argb;
+ }
+
+ public uint Lookup(int key)
+ {
+ return this.Colors[key];
+ }
+
+ private int HashPix(uint argb, int shift)
+ {
+ return (int)((argb * HashMul) >> shift);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/HTreeGroup.cs b/src/ImageSharp/Formats/WebP/HTreeGroup.cs
new file mode 100644
index 0000000000..c311601bb5
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/HTreeGroup.cs
@@ -0,0 +1,59 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Collections.Generic;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Huffman table group.
+ /// Includes special handling for the following cases:
+ /// - IsTrivialLiteral: one common literal base for RED/BLUE/ALPHA (not GREEN)
+ /// - IsTrivialCode: only 1 code (no bit is read from the bitstream)
+ /// - UsePackedTable: few enough literal symbols, so all the bit codes can fit into a small look-up table PackedTable[]
+ /// The common literal base, if applicable, is stored in 'LiteralArb'.
+ ///
+ internal class HTreeGroup
+ {
+ public HTreeGroup(uint packedTableSize)
+ {
+ this.HTrees = new List(WebPConstants.HuffmanCodesPerMetaCode);
+ this.PackedTable = new HuffmanCode[packedTableSize];
+ for (int i = 0; i < packedTableSize; i++)
+ {
+ this.PackedTable[i] = new HuffmanCode();
+ }
+ }
+
+ ///
+ /// Gets the Huffman trees. This has a maximum of HuffmanCodesPerMetaCode (5) entry's.
+ ///
+ public List HTrees { get; }
+
+ ///
+ /// Gets or sets a value indicating whether huffman trees for Red, Blue and Alpha Symbols are trivial (have a single code).
+ ///
+ public bool IsTrivialLiteral { get; set; }
+
+ ///
+ /// Gets or sets a the literal argb value of the pixel.
+ /// If IsTrivialLiteral is true, this is the ARGB value of the pixel, with Green channel being set to zero.
+ ///
+ public uint LiteralArb { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether there is only one code.
+ ///
+ public bool IsTrivialCode { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether to use packed table below for short literal code.
+ ///
+ public bool UsePackedTable { get; set; }
+
+ ///
+ /// Gets or sets table mapping input bits to packed values, or escape case to literal code.
+ ///
+ public HuffmanCode[] PackedTable { get; set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/HuffIndex.cs b/src/ImageSharp/Formats/WebP/HuffIndex.cs
new file mode 100644
index 0000000000..7e2b58a8e5
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/HuffIndex.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Five Huffman codes are used at each meta code.
+ ///
+ public static class HuffIndex
+ {
+ ///
+ /// Green + length prefix codes + color cache codes.
+ ///
+ public const int Green = 0;
+
+ ///
+ /// Red.
+ ///
+ public const int Red = 1;
+
+ ///
+ /// Blue.
+ ///
+ public const int Blue = 2;
+
+ ///
+ /// Alpha.
+ ///
+ public const int Alpha = 3;
+
+ ///
+ /// Distance prefix codes.
+ ///
+ public const int Dist = 4;
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/HuffmanCode.cs b/src/ImageSharp/Formats/WebP/HuffmanCode.cs
new file mode 100644
index 0000000000..ac6c5bec46
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/HuffmanCode.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Diagnostics;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// A classic way to do entropy coding where a smaller number of bits are used for more frequent codes.
+ ///
+ [DebuggerDisplay("BitsUsed: {BitsUsed}, Value: {Value}")]
+ internal class HuffmanCode
+ {
+ ///
+ /// Gets or sets the number of bits used for this symbol.
+ ///
+ public int BitsUsed { get; set; }
+
+ ///
+ /// Gets or sets the symbol value or table offset.
+ ///
+ public uint Value { get; set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/HuffmanUtils.cs b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs
new file mode 100644
index 0000000000..3cab68dd12
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/HuffmanUtils.cs
@@ -0,0 +1,216 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Utility functions related to creating the huffman tables.
+ ///
+ internal static class HuffmanUtils
+ {
+ public const int HuffmanTableBits = 8;
+
+ public const int HuffmanPackedBits = 6;
+
+ public const int HuffmanTableMask = (1 << HuffmanTableBits) - 1;
+
+ public const uint HuffmanPackedTableSize = 1u << HuffmanPackedBits;
+
+ public static int BuildHuffmanTable(Span table, int rootBits, int[] codeLengths, int codeLengthsSize)
+ {
+ Guard.MustBeGreaterThan(rootBits, 0, nameof(rootBits));
+ Guard.NotNull(codeLengths, nameof(codeLengths));
+ Guard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize));
+
+ // sorted[codeLengthsSize] is a pre-allocated array for sorting symbols by code length.
+ var sorted = new int[codeLengthsSize];
+ int totalSize = 1 << rootBits; // total size root table + 2nd level table.
+ int len; // current code length.
+ int symbol; // symbol index in original or sorted table.
+ var count = new int[WebPConstants.MaxAllowedCodeLength + 1]; // number of codes of each length.
+ var offset = new int[WebPConstants.MaxAllowedCodeLength + 1]; // offsets in sorted table for each length.
+
+ // Build histogram of code lengths.
+ for (symbol = 0; symbol < codeLengthsSize; ++symbol)
+ {
+ if (codeLengths[symbol] > WebPConstants.MaxAllowedCodeLength)
+ {
+ return 0;
+ }
+
+ count[codeLengths[symbol]]++;
+ }
+
+ // Error, all code lengths are zeros.
+ if (count[0] == codeLengthsSize)
+ {
+ return 0;
+ }
+
+ // Generate offsets into sorted symbol table by code length.
+ offset[1] = 0;
+ for (len = 1; len < WebPConstants.MaxAllowedCodeLength; ++len)
+ {
+ if (count[len] > (1 << len))
+ {
+ return 0;
+ }
+
+ offset[len + 1] = offset[len] + count[len];
+ }
+
+ // Sort symbols by length, by symbol order within each length.
+ for (symbol = 0; symbol < codeLengthsSize; ++symbol)
+ {
+ int symbolCodeLength = codeLengths[symbol];
+ if (codeLengths[symbol] > 0)
+ {
+ sorted[offset[symbolCodeLength]++] = symbol;
+ }
+ }
+
+ // Special case code with only one value.
+ if (offset[WebPConstants.MaxAllowedCodeLength] is 1)
+ {
+ var huffmanCode = new HuffmanCode()
+ {
+ BitsUsed = 0,
+ Value = (uint)sorted[0]
+ };
+ ReplicateValue(table, 1, totalSize, huffmanCode);
+ return totalSize;
+ }
+
+ int step; // step size to replicate values in current table
+ int low = -1; // low bits for current root entry
+ int mask = totalSize - 1; // mask for low bits
+ int key = 0; // reversed prefix code
+ int numNodes = 1; // number of Huffman tree nodes
+ int numOpen = 1; // number of open branches in current tree level
+ int tableBits = rootBits; // key length of current table
+ int tableSize = 1 << tableBits; // size of current table
+ symbol = 0;
+
+ // Fill in root table.
+ for (len = 1, step = 2; len <= rootBits; ++len, step <<= 1)
+ {
+ numOpen <<= 1;
+ numNodes += numOpen;
+ numOpen -= count[len];
+ if (numOpen < 0)
+ {
+ return 0;
+ }
+
+ for (; count[len] > 0; --count[len])
+ {
+ var huffmanCode = new HuffmanCode()
+ {
+ BitsUsed = len,
+ Value = (uint)sorted[symbol++]
+ };
+ ReplicateValue(table.Slice(key), step, tableSize, huffmanCode);
+ key = GetNextKey(key, len);
+ }
+ }
+
+ // Fill in 2nd level tables and add pointers to root table.
+ Span tableSpan = table;
+ int tablePos = 0;
+ for (len = rootBits + 1, step = 2; len <= WebPConstants.MaxAllowedCodeLength; ++len, step <<= 1)
+ {
+ numOpen <<= 1;
+ numNodes += numOpen;
+ numOpen -= count[len];
+ if (numOpen < 0)
+ {
+ return 0;
+ }
+
+ for (; count[len] > 0; --count[len])
+ {
+ if ((key & mask) != low)
+ {
+ tableSpan = tableSpan.Slice(tableSize);
+ tablePos += tableSize;
+ tableBits = NextTableBitSize(count, len, rootBits);
+ tableSize = 1 << tableBits;
+ totalSize += tableSize;
+ low = key & mask;
+ uint v = (uint)(tablePos - low);
+ table[low] = new HuffmanCode
+ {
+ BitsUsed = tableBits + rootBits,
+ Value = (uint)(tablePos - low)
+ };
+ }
+
+ var huffmanCode = new HuffmanCode
+ {
+ BitsUsed = len - rootBits,
+ Value = (uint)sorted[symbol++]
+ };
+ ReplicateValue(tableSpan.Slice(key >> rootBits), step, tableSize, huffmanCode);
+ key = GetNextKey(key, len);
+ }
+ }
+
+ return totalSize;
+ }
+
+ ///
+ /// Returns the table width of the next 2nd level table. count is the histogram of bit lengths for the remaining symbols,
+ /// len is the code length of the next processed symbol.
+ ///
+ private static int NextTableBitSize(int[] count, int len, int rootBits)
+ {
+ int left = 1 << (len - rootBits);
+ while (len < WebPConstants.MaxAllowedCodeLength)
+ {
+ left -= count[len];
+ if (left <= 0)
+ {
+ break;
+ }
+
+ ++len;
+ left <<= 1;
+ }
+
+ return len - rootBits;
+ }
+
+ ///
+ /// Stores code in table[0], table[step], table[2*step], ..., table[end].
+ /// Assumes that end is an integer multiple of step.
+ ///
+ private static void ReplicateValue(Span table, int step, int end, HuffmanCode code)
+ {
+ Guard.IsTrue(end % step == 0, nameof(end), "end must be a multiple of step");
+
+ do
+ {
+ end -= step;
+ table[end] = code;
+ }
+ while (end > 0);
+ }
+
+ ///
+ /// Returns reverse(reverse(key, len) + 1, len), where reverse(key, len) is the
+ /// bit-wise reversal of the len least significant bits of key.
+ ///
+ private static int GetNextKey(int key, int len)
+ {
+ int step = 1 << (len - 1);
+ while ((key & step) != 0)
+ {
+ step >>= 1;
+ }
+
+ return step != 0 ? (key & (step - 1)) + step : key;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs b/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs
new file mode 100644
index 0000000000..1ddbdbdc6d
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/IWebPDecoderOptions.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Image decoder for generating an image out of a webp stream.
+ ///
+ internal interface IWebPDecoderOptions
+ {
+ ///
+ /// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
+ ///
+ bool IgnoreMetadata { get; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/LoopFilter.cs b/src/ImageSharp/Formats/WebP/LoopFilter.cs
new file mode 100644
index 0000000000..8330ca593f
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/LoopFilter.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Enum for the different loop filters used. VP8 supports two types of loop filters.
+ ///
+ internal enum LoopFilter
+ {
+ ///
+ /// No filter is used.
+ ///
+ None = 0,
+
+ ///
+ /// Simple loop filter.
+ ///
+ Simple = 1,
+
+ ///
+ /// Complex loop filter.
+ ///
+ Complex = 2,
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/LosslessUtils.cs b/src/ImageSharp/Formats/WebP/LosslessUtils.cs
new file mode 100644
index 0000000000..600338e8ea
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/LosslessUtils.cs
@@ -0,0 +1,656 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.InteropServices;
+
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Utility functions for the lossless decoder.
+ ///
+ internal static class LosslessUtils
+ {
+ ///
+ /// Add green to blue and red channels (i.e. perform the inverse transform of 'subtract green').
+ ///
+ /// The pixel data to apply the transformation.
+ public static void AddGreenToBlueAndRed(Span pixelData)
+ {
+ for (int i = 0; i < pixelData.Length; i++)
+ {
+ uint argb = pixelData[i];
+ uint green = (argb >> 8) & 0xff;
+ uint redBlue = argb & 0x00ff00ffu;
+ redBlue += (green << 16) | green;
+ redBlue &= 0x00ff00ffu;
+ pixelData[i] = (argb & 0xff00ff00u) | redBlue;
+ }
+ }
+
+ ///
+ /// If there are not many unique pixel values, it may be more efficient to create a color index array and replace the pixel values by the array's indices.
+ /// This will reverse the color index transform.
+ ///
+ /// The transform data contains color table size and the entries in the color table.
+ /// The pixel data to apply the reverse transform on.
+ public static void ColorIndexInverseTransform(Vp8LTransform transform, Span pixelData)
+ {
+ int bitsPerPixel = 8 >> transform.Bits;
+ int width = transform.XSize;
+ int height = transform.YSize;
+ Span colorMap = transform.Data.GetSpan();
+ int decodedPixels = 0;
+ if (bitsPerPixel < 8)
+ {
+ int pixelsPerByte = 1 << transform.Bits;
+ int countMask = pixelsPerByte - 1;
+ int bitMask = (1 << bitsPerPixel) - 1;
+
+ var decodedPixelData = new uint[width * height];
+ int pixelDataPos = 0;
+ for (int y = 0; y < height; ++y)
+ {
+ uint packedPixels = 0;
+ for (int x = 0; x < width; ++x)
+ {
+ // We need to load fresh 'packed_pixels' once every
+ // 'pixelsPerByte' increments of x. Fortunately, pixelsPerByte
+ // is a power of 2, so can just use a mask for that, instead of
+ // decrementing a counter.
+ if ((x & countMask) is 0)
+ {
+ packedPixels = GetArgbIndex(pixelData[pixelDataPos++]);
+ }
+
+ decodedPixelData[decodedPixels++] = colorMap[(int)(packedPixels & bitMask)];
+ packedPixels >>= bitsPerPixel;
+ }
+ }
+
+ decodedPixelData.AsSpan().CopyTo(pixelData);
+
+ return;
+ }
+
+ for (int y = 0; y < height; ++y)
+ {
+ for (int x = 0; x < width; ++x)
+ {
+ uint colorMapIndex = GetArgbIndex(pixelData[decodedPixels]);
+ pixelData[decodedPixels] = colorMap[(int)colorMapIndex];
+ decodedPixels++;
+ }
+ }
+ }
+
+ ///
+ /// The goal of the color transform is to de-correlate the R, G and B values of each pixel.
+ /// Color transform keeps the green (G) value as it is, transforms red (R) based on green and transforms blue (B) based on green and then based on red.
+ ///
+ /// The transform data.
+ /// The pixel data to apply the inverse transform on.
+ public static void ColorSpaceInverseTransform(Vp8LTransform transform, Span pixelData)
+ {
+ int width = transform.XSize;
+ int yEnd = transform.YSize;
+ int tileWidth = 1 << transform.Bits;
+ int mask = tileWidth - 1;
+ int safeWidth = width & ~mask;
+ int remainingWidth = width - safeWidth;
+ int tilesPerRow = SubSampleSize(width, transform.Bits);
+ int y = 0;
+ int predRowIdxStart = (y >> transform.Bits) * tilesPerRow;
+ Span transformData = transform.Data.GetSpan();
+
+ int pixelPos = 0;
+ while (y < yEnd)
+ {
+ int predRowIdx = predRowIdxStart;
+ Vp8LMultipliers m = default(Vp8LMultipliers);
+ int srcSafeEnd = pixelPos + safeWidth;
+ int srcEnd = pixelPos + width;
+ while (pixelPos < srcSafeEnd)
+ {
+ uint colorCode = transformData[predRowIdx++];
+ ColorCodeToMultipliers(colorCode, ref m);
+ TransformColorInverse(m, pixelData, pixelPos, tileWidth);
+ pixelPos += tileWidth;
+ }
+
+ if (pixelPos < srcEnd)
+ {
+ uint colorCode = transformData[predRowIdx];
+ ColorCodeToMultipliers(colorCode, ref m);
+ TransformColorInverse(m, pixelData, pixelPos, remainingWidth);
+ pixelPos += remainingWidth;
+ }
+
+ ++y;
+ if ((y & mask) is 0)
+ {
+ predRowIdxStart += tilesPerRow;
+ }
+ }
+ }
+
+ ///
+ /// Reverses the color space transform.
+ ///
+ /// The color transform element.
+ /// The pixel data to apply the inverse transform on.
+ /// The start index of reverse transform.
+ /// The number of pixels to apply the transform.
+ public static void TransformColorInverse(Vp8LMultipliers m, Span pixelData, int start, int numPixels)
+ {
+ int end = start + numPixels;
+ for (int i = start; i < end; i++)
+ {
+ uint argb = pixelData[i];
+ sbyte green = (sbyte)(argb >> 8);
+ uint red = argb >> 16;
+ int newRed = (int)(red & 0xff);
+ int newBlue = (int)argb & 0xff;
+ newRed += ColorTransformDelta((sbyte)m.GreenToRed, (sbyte)green);
+ newRed &= 0xff;
+ newBlue += ColorTransformDelta((sbyte)m.GreenToBlue, (sbyte)green);
+ newBlue += ColorTransformDelta((sbyte)m.RedToBlue, (sbyte)newRed);
+ newBlue &= 0xff;
+
+ pixelData[i] = (argb & 0xff00ff00u) | ((uint)newRed << 16) | (uint)newBlue;
+ }
+ }
+
+ ///
+ /// This will reverse the predictor transform.
+ /// The predictor transform can be used to reduce entropy by exploiting the fact that neighboring pixels are often correlated.
+ /// In the predictor transform, the current pixel value is predicted from the pixels already decoded (in scan-line order) and only the residual value (actual - predicted) is encoded.
+ /// The prediction mode determines the type of prediction to use. The image is divided into squares and all the pixels in a square use same prediction mode.
+ ///
+ /// The transform data.
+ /// The pixel data to apply the inverse transform.
+ /// The resulting pixel data with the reversed transformation data.
+ public static void PredictorInverseTransform(Vp8LTransform transform, Span pixelData, Span output)
+ {
+ int processedPixels = 0;
+ int yStart = 0;
+ int width = transform.XSize;
+ Span transformData = transform.Data.GetSpan();
+
+ // First Row follows the L (mode=1) mode.
+ PredictorAdd0(pixelData, processedPixels, 1, output);
+ PredictorAdd1(pixelData, 1, width - 1, output);
+ processedPixels += width;
+ yStart++;
+
+ int y = yStart;
+ int yEnd = transform.YSize;
+ int tileWidth = 1 << transform.Bits;
+ int mask = tileWidth - 1;
+ int tilesPerRow = SubSampleSize(width, transform.Bits);
+ int predictorModeIdxBase = (y >> transform.Bits) * tilesPerRow;
+ while (y < yEnd)
+ {
+ int predictorModeIdx = predictorModeIdxBase;
+ int x = 1;
+
+ // First pixel follows the T (mode=2) mode.
+ PredictorAdd2(pixelData, processedPixels, 1, width, output);
+
+ while (x < width)
+ {
+ uint predictorMode = (transformData[predictorModeIdx++] >> 8) & 0xf;
+ int xEnd = (x & ~mask) + tileWidth;
+ if (xEnd > width)
+ {
+ xEnd = width;
+ }
+
+ // There are 14 different prediction modes.
+ // In each prediction mode, the current pixel value is predicted from one or more neighboring pixels whose values are already known.
+ int startIdx = processedPixels + x;
+ int numberOfPixels = xEnd - x;
+ switch (predictorMode)
+ {
+ case 0:
+ PredictorAdd0(pixelData, startIdx, numberOfPixels, output);
+ break;
+ case 1:
+ PredictorAdd1(pixelData, startIdx, numberOfPixels, output);
+ break;
+ case 2:
+ PredictorAdd2(pixelData, startIdx, numberOfPixels, width, output);
+ break;
+ case 3:
+ PredictorAdd3(pixelData, startIdx, numberOfPixels, width, output);
+ break;
+ case 4:
+ PredictorAdd4(pixelData, startIdx, numberOfPixels, width, output);
+ break;
+ case 5:
+ PredictorAdd5(pixelData, startIdx, numberOfPixels, width, output);
+ break;
+ case 6:
+ PredictorAdd6(pixelData, startIdx, numberOfPixels, width, output);
+ break;
+ case 7:
+ PredictorAdd7(pixelData, startIdx, numberOfPixels, width, output);
+ break;
+ case 8:
+ PredictorAdd8(pixelData, startIdx, numberOfPixels, width, output);
+ break;
+ case 9:
+ PredictorAdd9(pixelData, startIdx, numberOfPixels, width, output);
+ break;
+ case 10:
+ PredictorAdd10(pixelData, startIdx, numberOfPixels, width, output);
+ break;
+ case 11:
+ PredictorAdd11(pixelData, startIdx, numberOfPixels, width, output);
+ break;
+ case 12:
+ PredictorAdd12(pixelData, startIdx, numberOfPixels, width, output);
+ break;
+ case 13:
+ PredictorAdd13(pixelData, startIdx, numberOfPixels, width, output);
+ break;
+ }
+
+ x = xEnd;
+ }
+
+ processedPixels += width;
+ ++y;
+ if ((y & mask) is 0)
+ {
+ // Use the same mask, since tiles are squares.
+ predictorModeIdxBase += tilesPerRow;
+ }
+ }
+
+ output.CopyTo(pixelData);
+ }
+
+ private static void PredictorAdd0(Span input, int startIdx, int numberOfPixels, Span output)
+ {
+ int endIdx = startIdx + numberOfPixels;
+ for (int x = startIdx; x < endIdx; ++x)
+ {
+ uint pred = Predictor0();
+ output[x] = AddPixels(input[x], pred);
+ }
+ }
+
+ private static void PredictorAdd1(Span input, int startIdx, int numberOfPixels, Span output)
+ {
+ int endIdx = startIdx + numberOfPixels;
+ uint left = output[startIdx - 1];
+ for (int x = startIdx; x < endIdx; ++x)
+ {
+ output[x] = left = AddPixels(input[x], left);
+ }
+ }
+
+ private static void PredictorAdd2(Span input, int startIdx, int numberOfPixels, int width, Span output)
+ {
+ int endIdx = startIdx + numberOfPixels;
+ int offset = 0;
+ for (int x = startIdx; x < endIdx; ++x)
+ {
+ uint pred = Predictor2(output[x - 1], output, startIdx - width + offset++);
+ output[x] = AddPixels(input[x], pred);
+ }
+ }
+
+ private static void PredictorAdd3(Span input, int startIdx, int numberOfPixels, int width, Span output)
+ {
+ int endIdx = startIdx + numberOfPixels;
+ int offset = 0;
+ for (int x = startIdx; x < endIdx; ++x)
+ {
+ uint pred = Predictor3(output[x - 1], output, startIdx - width + offset++);
+ output[x] = AddPixels(input[x], pred);
+ }
+ }
+
+ private static void PredictorAdd4(Span input, int startIdx, int numberOfPixels, int width, Span output)
+ {
+ int endIdx = startIdx + numberOfPixels;
+ int offset = 0;
+ for (int x = startIdx; x < endIdx; ++x)
+ {
+ uint pred = Predictor4(output[x - 1], output, startIdx - width + offset++);
+ output[x] = AddPixels(input[x], pred);
+ }
+ }
+
+ private static void PredictorAdd5(Span input, int startIdx, int numberOfPixels, int width, Span output)
+ {
+ int endIdx = startIdx + numberOfPixels;
+ int offset = 0;
+ for (int x = startIdx; x < endIdx; ++x)
+ {
+ uint pred = Predictor5(output[x - 1], output, startIdx - width + offset++);
+ output[x] = AddPixels(input[x], pred);
+ }
+ }
+
+ private static void PredictorAdd6(Span input, int startIdx, int numberOfPixels, int width, Span output)
+ {
+ int endIdx = startIdx + numberOfPixels;
+ int offset = 0;
+ for (int x = startIdx; x < endIdx; ++x)
+ {
+ uint pred = Predictor6(output[x - 1], output, startIdx - width + offset++);
+ output[x] = AddPixels(input[x], pred);
+ }
+ }
+
+ private static void PredictorAdd7(Span input, int startIdx, int numberOfPixels, int width, Span output)
+ {
+ int endIdx = startIdx + numberOfPixels;
+ int offset = 0;
+ for (int x = startIdx; x < endIdx; ++x)
+ {
+ uint pred = Predictor7(output[x - 1], output, startIdx - width + offset++);
+ output[x] = AddPixels(input[x], pred);
+ }
+ }
+
+ private static void PredictorAdd8(Span input, int startIdx, int numberOfPixels, int width, Span output)
+ {
+ int endIdx = startIdx + numberOfPixels;
+ int offset = 0;
+ for (int x = startIdx; x < endIdx; ++x)
+ {
+ uint pred = Predictor8(output[x - 1], output, startIdx - width + offset++);
+ output[x] = AddPixels(input[x], pred);
+ }
+ }
+
+ private static void PredictorAdd9(Span input, int startIdx, int numberOfPixels, int width, Span output)
+ {
+ int endIdx = startIdx + numberOfPixels;
+ int offset = 0;
+ for (int x = startIdx; x < endIdx; ++x)
+ {
+ uint pred = Predictor9(output[x - 1], output, startIdx - width + offset++);
+ output[x] = AddPixels(input[x], pred);
+ }
+ }
+
+ private static void PredictorAdd10(Span input, int startIdx, int numberOfPixels, int width, Span output)
+ {
+ int endIdx = startIdx + numberOfPixels;
+ int offset = 0;
+ for (int x = startIdx; x < endIdx; ++x)
+ {
+ uint pred = Predictor10(output[x - 1], output, startIdx - width + offset++);
+ output[x] = AddPixels(input[x], pred);
+ }
+ }
+
+ private static void PredictorAdd11(Span input, int startIdx, int numberOfPixels, int width, Span output)
+ {
+ int endIdx = startIdx + numberOfPixels;
+ int offset = 0;
+ for (int x = startIdx; x < endIdx; ++x)
+ {
+ uint pred = Predictor11(output[x - 1], output, startIdx - width + offset++);
+ output[x] = AddPixels(input[x], pred);
+ }
+ }
+
+ private static void PredictorAdd12(Span input, int startIdx, int numberOfPixels, int width, Span output)
+ {
+ int endIdx = startIdx + numberOfPixels;
+ int offset = 0;
+ for (int x = startIdx; x < endIdx; ++x)
+ {
+ uint pred = Predictor12(output[x - 1], output, startIdx - width + offset++);
+ output[x] = AddPixels(input[x], pred);
+ }
+ }
+
+ private static void PredictorAdd13(Span input, int startIdx, int numberOfPixels, int width, Span output)
+ {
+ int endIdx = startIdx + numberOfPixels;
+ int offset = 0;
+ for (int x = startIdx; x < endIdx; ++x)
+ {
+ uint pred = Predictor13(output[x - 1], output, startIdx - width + offset++);
+ output[x] = AddPixels(input[x], pred);
+ }
+ }
+
+ private static uint Predictor0()
+ {
+ return WebPConstants.ArgbBlack;
+ }
+
+ private static uint Predictor1(uint left, Span top)
+ {
+ return left;
+ }
+
+ private static uint Predictor2(uint left, Span top, int idx)
+ {
+ return top[idx];
+ }
+
+ private static uint Predictor3(uint left, Span top, int idx)
+ {
+ return top[idx + 1];
+ }
+
+ private static uint Predictor4(uint left, Span top, int idx)
+ {
+ return top[idx - 1];
+ }
+
+ private static uint Predictor5(uint left, Span top, int idx)
+ {
+ uint pred = Average3(left, top[idx], top[idx + 1]);
+ return pred;
+ }
+
+ private static uint Predictor6(uint left, Span top, int idx)
+ {
+ uint pred = Average2(left, top[idx - 1]);
+ return pred;
+ }
+
+ private static uint Predictor7(uint left, Span top, int idx)
+ {
+ uint pred = Average2(left, top[idx]);
+ return pred;
+ }
+
+ private static uint Predictor8(uint left, Span top, int idx)
+ {
+ uint pred = Average2(top[idx - 1], top[idx]);
+ return pred;
+ }
+
+ private static uint Predictor9(uint left, Span top, int idx)
+ {
+ uint pred = Average2(top[idx], top[idx + 1]);
+ return pred;
+ }
+
+ private static uint Predictor10(uint left, Span top, int idx)
+ {
+ uint pred = Average4(left, top[idx - 1], top[idx], top[idx + 1]);
+ return pred;
+ }
+
+ private static uint Predictor11(uint left, Span top, int idx)
+ {
+ uint pred = Select(top[idx], left, top[idx - 1]);
+ return pred;
+ }
+
+ private static uint Predictor12(uint left, Span top, int idx)
+ {
+ uint pred = ClampedAddSubtractFull(left, top[idx], top[idx - 1]);
+ return pred;
+ }
+
+ private static uint Predictor13(uint left, Span top, int idx)
+ {
+ uint pred = ClampedAddSubtractHalf(left, top[idx], top[idx - 1]);
+ return pred;
+ }
+
+ private static uint ClampedAddSubtractFull(uint c0, uint c1, uint c2)
+ {
+ int a = AddSubtractComponentFull((int)(c0 >> 24), (int)(c1 >> 24), (int)(c2 >> 24));
+ int r = AddSubtractComponentFull(
+ (int)((c0 >> 16) & 0xff),
+ (int)((c1 >> 16) & 0xff),
+ (int)((c2 >> 16) & 0xff));
+ int g = AddSubtractComponentFull(
+ (int)((c0 >> 8) & 0xff),
+ (int)((c1 >> 8) & 0xff),
+ (int)((c2 >> 8) & 0xff));
+ int b = AddSubtractComponentFull((int)(c0 & 0xff), (int)(c1 & 0xff), (int)(c2 & 0xff));
+ return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b;
+ }
+
+ private static uint ClampedAddSubtractHalf(uint c0, uint c1, uint c2)
+ {
+ uint ave = Average2(c0, c1);
+ int a = AddSubtractComponentHalf((int)(ave >> 24), (int)(c2 >> 24));
+ int r = AddSubtractComponentHalf((int)((ave >> 16) & 0xff), (int)((c2 >> 16) & 0xff));
+ int g = AddSubtractComponentHalf((int)((ave >> 8) & 0xff), (int)((c2 >> 8) & 0xff));
+ int b = AddSubtractComponentHalf((int)(ave & 0xff), (int)(c2 & 0xff));
+ return ((uint)a << 24) | ((uint)r << 16) | ((uint)g << 8) | (uint)b;
+ }
+
+ private static int AddSubtractComponentHalf(int a, int b)
+ {
+ return (int)Clip255((uint)(a + ((a - b) / 2)));
+ }
+
+ private static int AddSubtractComponentFull(int a, int b, int c)
+ {
+ return (int)Clip255((uint)(a + b - c));
+ }
+
+ private static uint Clip255(uint a)
+ {
+ if (a < 256)
+ {
+ return a;
+ }
+
+ return ~a >> 24;
+ }
+
+ private static uint Select(uint a, uint b, uint c)
+ {
+ int paMinusPb =
+ Sub3((int)(a >> 24), (int)(b >> 24), (int)(c >> 24)) +
+ Sub3((int)((a >> 16) & 0xff), (int)((b >> 16) & 0xff), (int)((c >> 16) & 0xff)) +
+ Sub3((int)((a >> 8) & 0xff), (int)((b >> 8) & 0xff), (int)((c >> 8) & 0xff)) +
+ Sub3((int)(a & 0xff), (int)(b & 0xff), (int)(c & 0xff));
+ return (paMinusPb <= 0) ? a : b;
+ }
+
+ private static int Sub3(int a, int b, int c)
+ {
+ int pb = b - c;
+ int pa = a - c;
+ return Math.Abs(pb) - Math.Abs(pa);
+ }
+
+ private static uint Average2(uint a0, uint a1)
+ {
+ return (((a0 ^ a1) & 0xfefefefeu) >> 1) + (a0 & a1);
+ }
+
+ private static uint Average3(uint a0, uint a1, uint a2)
+ {
+ return Average2(Average2(a0, a2), a1);
+ }
+
+ private static uint Average4(uint a0, uint a1, uint a2, uint a3)
+ {
+ return Average2(Average2(a0, a1), Average2(a2, a3));
+ }
+
+ ///
+ /// Computes sampled size of 'size' when sampling using 'sampling bits'.
+ ///
+ public static int SubSampleSize(int size, int samplingBits)
+ {
+ return (size + (1 << samplingBits) - 1) >> samplingBits;
+ }
+
+ ///
+ /// Sum of each component, mod 256.
+ ///
+ private static uint AddPixels(uint a, uint b)
+ {
+ uint alphaAndGreen = (a & 0xff00ff00u) + (b & 0xff00ff00u);
+ uint redAndBlue = (a & 0x00ff00ffu) + (b & 0x00ff00ffu);
+ return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu);
+ }
+
+ ///
+ /// Difference of each component, mod 256.
+ ///
+ private static uint SubPixels(uint a, uint b)
+ {
+ uint alphaAndGreen = 0x00ff00ffu + (a & 0xff00ff00u) - (b & 0xff00ff00u);
+ uint redAndBlue = 0xff00ff00u + (a & 0x00ff00ffu) - (b & 0x00ff00ffu);
+ return (alphaAndGreen & 0xff00ff00u) | (redAndBlue & 0x00ff00ffu);
+ }
+
+ private static uint GetArgbIndex(uint idx)
+ {
+ return (idx >> 8) & 0xff;
+ }
+
+ public static void ExpandColorMap(int numColors, Span transformData, Span newColorMap)
+ {
+ newColorMap[0] = transformData[0];
+ Span data = MemoryMarshal.Cast(transformData);
+ Span newData = MemoryMarshal.Cast(newColorMap);
+ int i;
+ for (i = 4; i < 4 * numColors; ++i)
+ {
+ // Equivalent to AddPixelEq(), on a byte-basis.
+ newData[i] = (byte)((data[i] + newData[i - 4]) & 0xff);
+ }
+
+ for (; i < 4 * newColorMap.Length; ++i)
+ {
+ newData[i] = 0; // black tail.
+ }
+ }
+
+ private static int ColorTransformDelta(sbyte colorPred, sbyte color)
+ {
+ return ((int)colorPred * color) >> 5;
+ }
+
+ private static void ColorCodeToMultipliers(uint colorCode, ref Vp8LMultipliers m)
+ {
+ m.GreenToRed = (byte)(colorCode & 0xff);
+ m.GreenToBlue = (byte)((colorCode >> 8) & 0xff);
+ m.RedToBlue = (byte)((colorCode >> 16) & 0xff);
+ }
+
+ internal struct Vp8LMultipliers
+ {
+ public byte GreenToRed;
+
+ public byte GreenToBlue;
+
+ public byte RedToBlue;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/LossyUtils.cs b/src/ImageSharp/Formats/WebP/LossyUtils.cs
new file mode 100644
index 0000000000..7222cfa7b3
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/LossyUtils.cs
@@ -0,0 +1,912 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers.Binary;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ internal static class LossyUtils
+ {
+ private static void Put16(int v, Span dst)
+ {
+ for (int j = 0; j < 16; ++j)
+ {
+ Memset(dst.Slice(j * WebPConstants.Bps), (byte)v, 0, 16);
+ }
+ }
+
+ public static void DC16(Span dst, Span yuv, int offset)
+ {
+ int dc = 16;
+ for (int j = 0; j < 16; ++j)
+ {
+ // DC += dst[-1 + j * BPS] + dst[j - BPS];
+ dc += yuv[offset - 1 + (j * WebPConstants.Bps)] + yuv[offset + j - WebPConstants.Bps];
+ }
+
+ Put16(dc >> 5, dst);
+ }
+
+ public static void TM16(Span dst, Span yuv, int offset)
+ {
+ TrueMotion(dst, yuv, offset, 16);
+ }
+
+ public static void VE16(Span dst, Span yuv, int offset)
+ {
+ // vertical
+ Span src = yuv.Slice(offset - WebPConstants.Bps, 16);
+ for (int j = 0; j < 16; ++j)
+ {
+ // memcpy(dst + j * BPS, dst - BPS, 16);
+ src.CopyTo(dst.Slice(j * WebPConstants.Bps));
+ }
+ }
+
+ public static void HE16(Span dst, Span yuv, int offset)
+ {
+ // horizontal
+ for (int j = 16; j > 0; --j)
+ {
+ // memset(dst, dst[-1], 16);
+ byte v = yuv[offset - 1];
+ Memset(dst, v, 0, 16);
+ offset += WebPConstants.Bps;
+ dst = dst.Slice(WebPConstants.Bps);
+ }
+ }
+
+ public static void DC16NoTop(Span dst, Span yuv, int offset)
+ {
+ // DC with top samples not available.
+ int dc = 8;
+ for (int j = 0; j < 16; ++j)
+ {
+ // DC += dst[-1 + j * BPS];
+ dc += yuv[-1 + (j * WebPConstants.Bps) + offset];
+ }
+
+ Put16(dc >> 4, dst);
+ }
+
+ public static void DC16NoLeft(Span dst, Span yuv, int offset)
+ {
+ // DC with left samples not available.
+ int dc = 8;
+ for (int i = 0; i < 16; ++i)
+ {
+ // DC += dst[i - BPS];
+ dc += yuv[i - WebPConstants.Bps + offset];
+ }
+
+ Put16(dc >> 4, dst);
+ }
+
+ public static void DC16NoTopLeft(Span dst)
+ {
+ // DC with no top and left samples.
+ Put16(0x80, dst);
+ }
+
+ public static void DC8uv(Span dst, Span yuv, int offset)
+ {
+ int dc0 = 8;
+ for (int i = 0; i < 8; ++i)
+ {
+ // dc0 += dst[i - BPS] + dst[-1 + i * BPS];
+ dc0 += yuv[offset + i - WebPConstants.Bps] + yuv[offset - 1 + (i * WebPConstants.Bps)];
+ }
+
+ Put8x8uv((byte)(dc0 >> 4), dst);
+ }
+
+ public static void TM8uv(Span dst, Span yuv, int offset)
+ {
+ // TrueMotion
+ TrueMotion(dst, yuv, offset, 8);
+ }
+
+ public static void VE8uv(Span dst, Span yuv, int offset)
+ {
+ // vertical
+ Span src = yuv.Slice(offset - WebPConstants.Bps, 8);
+
+ for (int j = 0; j < 8; ++j)
+ {
+ // memcpy(dst + j * BPS, dst - BPS, 8);
+ src.CopyTo(dst.Slice(j * WebPConstants.Bps));
+ }
+ }
+
+ public static void HE8uv(Span dst, Span yuv, int offset)
+ {
+ // horizontal
+ for (int j = 0; j < 8; ++j)
+ {
+ // memset(dst, dst[-1], 8);
+ // dst += BPS;
+ byte v = yuv[offset - 1];
+ Memset(dst, v, 0, 8);
+ dst = dst.Slice(WebPConstants.Bps);
+ offset += WebPConstants.Bps;
+ }
+ }
+
+ public static void DC8uvNoTop(Span dst, Span yuv, int offset)
+ {
+ // DC with no top samples.
+ int dc0 = 4;
+ for (int i = 0; i < 8; ++i)
+ {
+ // dc0 += dst[-1 + i * BPS];
+ dc0 += yuv[offset - 1 + (i * WebPConstants.Bps)];
+ }
+
+ Put8x8uv((byte)(dc0 >> 3), dst);
+ }
+
+ public static void DC8uvNoLeft(Span dst, Span yuv, int offset)
+ {
+ // DC with no left samples.
+ int dc0 = 4;
+ for (int i = 0; i < 8; ++i)
+ {
+ // dc0 += dst[i - BPS];
+ dc0 += yuv[offset + i - WebPConstants.Bps];
+ }
+
+ Put8x8uv((byte)(dc0 >> 3), dst);
+ }
+
+ public static void DC8uvNoTopLeft(Span dst)
+ {
+ // DC with nothing.
+ Put8x8uv(0x80, dst);
+ }
+
+ public static void DC4(Span dst, Span yuv, int offset)
+ {
+ int dc = 4;
+ for (int i = 0; i < 4; ++i)
+ {
+ dc += yuv[offset + i - WebPConstants.Bps] + yuv[offset - 1 + (i * WebPConstants.Bps)];
+ }
+
+ dc >>= 3;
+ for (int i = 0; i < 4; ++i)
+ {
+ Memset(dst, (byte)dc, i * WebPConstants.Bps, 4);
+ }
+ }
+
+ public static void TM4(Span dst, Span yuv, int offset)
+ {
+ TrueMotion(dst, yuv, offset, 4);
+ }
+
+ public static void VE4(Span dst, Span yuv, int offset)
+ {
+ // vertical
+ int topOffset = offset - WebPConstants.Bps;
+ byte[] vals =
+ {
+ Avg3(yuv[topOffset - 1], yuv[topOffset], yuv[topOffset + 1]),
+ Avg3(yuv[topOffset], yuv[topOffset + 1], yuv[topOffset + 2]),
+ Avg3(yuv[topOffset + 1], yuv[topOffset + 2], yuv[topOffset + 3]),
+ Avg3(yuv[topOffset + 2], yuv[topOffset + 3], yuv[topOffset + 4])
+ };
+
+ for (int i = 0; i < 4; ++i)
+ {
+ vals.CopyTo(dst.Slice(i * WebPConstants.Bps));
+ }
+ }
+
+ public static void HE4(Span dst, Span yuv, int offset)
+ {
+ // horizontal
+ byte a = yuv[offset - 1 - WebPConstants.Bps];
+ byte b = yuv[offset - 1];
+ byte c = yuv[offset - 1 + WebPConstants.Bps];
+ byte d = yuv[offset - 1 + (2 * WebPConstants.Bps)];
+ byte e = yuv[offset - 1 + (3 * WebPConstants.Bps)];
+ uint val = 0x01010101U * Avg3(a, b, c);
+ BinaryPrimitives.WriteUInt32BigEndian(dst, val);
+ val = 0x01010101U * Avg3(b, c, d);
+ BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(WebPConstants.Bps), val);
+ val = 0x01010101U * Avg3(c, d, e);
+ BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(2 * WebPConstants.Bps), val);
+ val = 0x01010101U * Avg3(d, e, e);
+ BinaryPrimitives.WriteUInt32BigEndian(dst.Slice(3 * WebPConstants.Bps), val);
+ }
+
+ public static void RD4(Span dst, Span yuv, int offset)
+ {
+ // Down-right
+ byte i = yuv[offset - 1];
+ byte j = yuv[offset - 1 + (1 * WebPConstants.Bps)];
+ byte k = yuv[offset - 1 + (2 * WebPConstants.Bps)];
+ byte l = yuv[offset - 1 + (3 * WebPConstants.Bps)];
+ byte x = yuv[offset - 1 - WebPConstants.Bps];
+ byte a = yuv[offset - WebPConstants.Bps];
+ byte b = yuv[offset + 1 - WebPConstants.Bps];
+ byte c = yuv[offset + 2 - WebPConstants.Bps];
+ byte d = yuv[offset + 3 - WebPConstants.Bps];
+
+ Dst(dst, 0, 3, Avg3(j, k, l));
+ byte ijk = Avg3(i, j, k);
+ Dst(dst, 1, 3, ijk);
+ Dst(dst, 0, 2, ijk);
+ byte xij = Avg3(x, i, j);
+ Dst(dst, 2, 3, xij);
+ Dst(dst, 1, 2, xij);
+ Dst(dst, 0, 1, xij);
+ byte axi = Avg3(a, x, i);
+ Dst(dst, 3, 3, axi);
+ Dst(dst, 2, 2, axi);
+ Dst(dst, 1, 1, axi);
+ Dst(dst, 0, 0, axi);
+ byte bax = Avg3(b, a, x);
+ Dst(dst, 3, 2, bax);
+ Dst(dst, 2, 1, bax);
+ Dst(dst, 1, 0, bax);
+ byte cba = Avg3(c, b, a);
+ Dst(dst, 3, 1, cba);
+ Dst(dst, 2, 0, cba);
+ Dst(dst, 3, 0, Avg3(d, c, b));
+ }
+
+ public static void VR4(Span dst, Span yuv, int offset)
+ {
+ // Vertical-Right
+ byte i = yuv[offset - 1];
+ byte j = yuv[offset - 1 + (1 * WebPConstants.Bps)];
+ byte k = yuv[offset - 1 + (2 * WebPConstants.Bps)];
+ byte x = yuv[offset - 1 - WebPConstants.Bps];
+ byte a = yuv[offset - WebPConstants.Bps];
+ byte b = yuv[offset + 1 - WebPConstants.Bps];
+ byte c = yuv[offset + 2 - WebPConstants.Bps];
+ byte d = yuv[offset + 3 - WebPConstants.Bps];
+
+ byte xa = Avg2(x, a);
+ Dst(dst, 0, 0, xa);
+ Dst(dst, 1, 2, xa);
+ byte ab = Avg2(a, b);
+ Dst(dst, 1, 0, ab);
+ Dst(dst, 2, 2, ab);
+ byte bc = Avg2(b, c);
+ Dst(dst, 2, 0, bc);
+ Dst(dst, 3, 2, bc);
+ Dst(dst, 3, 0, Avg2(c, d));
+ Dst(dst, 0, 3, Avg3(k, j, i));
+ Dst(dst, 0, 2, Avg3(j, i, x));
+ byte ixa = Avg3(i, x, a);
+ Dst(dst, 0, 1, ixa);
+ Dst(dst, 1, 3, ixa);
+ byte xab = Avg3(x, a, b);
+ Dst(dst, 1, 1, xab);
+ Dst(dst, 2, 3, xab);
+ byte abc = Avg3(a, b, c);
+ Dst(dst, 2, 1, abc);
+ Dst(dst, 3, 3, abc);
+ Dst(dst, 3, 1, Avg3(b, c, d));
+ }
+
+ public static void LD4(Span dst, Span yuv, int offset)
+ {
+ // Down-Left
+ byte a = yuv[offset - WebPConstants.Bps];
+ byte b = yuv[offset + 1 - WebPConstants.Bps];
+ byte c = yuv[offset + 2 - WebPConstants.Bps];
+ byte d = yuv[offset + 3 - WebPConstants.Bps];
+ byte e = yuv[offset + 4 - WebPConstants.Bps];
+ byte f = yuv[offset + 5 - WebPConstants.Bps];
+ byte g = yuv[offset + 6 - WebPConstants.Bps];
+ byte h = yuv[offset + 7 - WebPConstants.Bps];
+
+ Dst(dst, 0, 0, Avg3(a, b, c));
+ byte bcd = Avg3(b, c, d);
+ Dst(dst, 1, 0, bcd);
+ Dst(dst, 0, 1, bcd);
+ byte cde = Avg3(c, d, e);
+ Dst(dst, 2, 0, cde);
+ Dst(dst, 1, 1, cde);
+ Dst(dst, 0, 2, cde);
+ byte def = Avg3(d, e, f);
+ Dst(dst, 3, 0, def);
+ Dst(dst, 2, 1, def);
+ Dst(dst, 1, 2, def);
+ Dst(dst, 0, 3, def);
+ byte efg = Avg3(e, f, g);
+ Dst(dst, 3, 1, efg);
+ Dst(dst, 2, 2, efg);
+ Dst(dst, 1, 3, efg);
+ byte fgh = Avg3(f, g, h);
+ Dst(dst, 3, 2, fgh);
+ Dst(dst, 2, 3, fgh);
+ Dst(dst, 3, 3, Avg3(g, h, h));
+ }
+
+ public static void VL4(Span dst, Span yuv, int offset)
+ {
+ // Vertical-Left
+ byte a = yuv[offset - WebPConstants.Bps];
+ byte b = yuv[offset + 1 - WebPConstants.Bps];
+ byte c = yuv[offset + 2 - WebPConstants.Bps];
+ byte d = yuv[offset + 3 - WebPConstants.Bps];
+ byte e = yuv[offset + 4 - WebPConstants.Bps];
+ byte f = yuv[offset + 5 - WebPConstants.Bps];
+ byte g = yuv[offset + 6 - WebPConstants.Bps];
+ byte h = yuv[offset + 7 - WebPConstants.Bps];
+
+ Dst(dst, 0, 0, Avg2(a, b));
+ byte bc = Avg2(b, c);
+ Dst(dst, 1, 0, bc);
+ Dst(dst, 0, 2, bc);
+ byte cd = Avg2(c, d);
+ Dst(dst, 2, 0, cd);
+ Dst(dst, 1, 2, cd);
+ byte de = Avg2(d, e);
+ Dst(dst, 3, 0, de);
+ Dst(dst, 2, 2, de);
+ Dst(dst, 0, 1, Avg3(a, b, c));
+ byte bcd = Avg3(b, c, d);
+ Dst(dst, 1, 1, bcd);
+ Dst(dst, 0, 3, bcd);
+ byte cde = Avg3(c, d, e);
+ Dst(dst, 2, 1, cde);
+ Dst(dst, 1, 3, cde);
+ byte def = Avg3(d, e, f);
+ Dst(dst, 3, 1, def);
+ Dst(dst, 2, 3, def);
+ Dst(dst, 3, 2, Avg3(e, f, g));
+ Dst(dst, 3, 3, Avg3(f, g, h));
+ }
+
+ public static void HD4(Span dst, Span yuv, int offset)
+ {
+ // Horizontal-Down
+ byte i = yuv[offset - 1];
+ byte j = yuv[offset - 1 + (1 * WebPConstants.Bps)];
+ byte k = yuv[offset - 1 + (2 * WebPConstants.Bps)];
+ byte l = yuv[offset - 1 + (3 * WebPConstants.Bps)];
+ byte x = yuv[offset - 1 - WebPConstants.Bps];
+ byte a = yuv[offset - WebPConstants.Bps];
+ byte b = yuv[offset + 1 - WebPConstants.Bps];
+ byte c = yuv[offset + 2 - WebPConstants.Bps];
+
+ byte ix = Avg2(i, x);
+ Dst(dst, 0, 0, ix);
+ Dst(dst, 2, 1, ix);
+ byte ji = Avg2(j, i);
+ Dst(dst, 0, 1, ji);
+ Dst(dst, 2, 2, ji);
+ byte kj = Avg2(k, j);
+ Dst(dst, 0, 2, kj);
+ Dst(dst, 2, 3, kj);
+ Dst(dst, 0, 3, Avg2(l, k));
+ Dst(dst, 3, 0, Avg3(a, b, c));
+ Dst(dst, 2, 0, Avg3(x, a, b));
+ byte ixa = Avg3(i, x, a);
+ Dst(dst, 1, 0, ixa);
+ Dst(dst, 3, 1, ixa);
+ byte jix = Avg3(j, i, x);
+ Dst(dst, 1, 1, jix);
+ Dst(dst, 3, 2, jix);
+ byte kji = Avg3(k, j, i);
+ Dst(dst, 1, 2, kji);
+ Dst(dst, 3, 3, kji);
+ Dst(dst, 1, 3, Avg3(l, k, j));
+ }
+
+ public static void HU4(Span dst, Span yuv, int offset)
+ {
+ // Horizontal-Up
+ byte i = yuv[offset - 1];
+ byte j = yuv[offset - 1 + (1 * WebPConstants.Bps)];
+ byte k = yuv[offset - 1 + (2 * WebPConstants.Bps)];
+ byte l = yuv[offset - 1 + (3 * WebPConstants.Bps)];
+
+ Dst(dst, 0, 0, Avg2(i, j));
+ byte jk = Avg2(j, k);
+ Dst(dst, 2, 0, jk);
+ Dst(dst, 0, 1, jk);
+ byte kl = Avg2(k, l);
+ Dst(dst, 2, 1, kl);
+ Dst(dst, 0, 2, kl);
+ Dst(dst, 1, 0, Avg3(i, j, k));
+ byte jkl = Avg3(j, k, l);
+ Dst(dst, 3, 0, jkl);
+ Dst(dst, 1, 1, jkl);
+ byte kll = Avg3(k, l, l);
+ Dst(dst, 3, 1, kll);
+ Dst(dst, 1, 2, kll);
+ Dst(dst, 3, 2, l);
+ Dst(dst, 2, 2, l);
+ Dst(dst, 0, 3, l);
+ Dst(dst, 1, 3, l);
+ Dst(dst, 2, 3, l);
+ Dst(dst, 3, 3, l);
+ }
+
+ public static void Transform(Span src, Span dst, bool doTwo)
+ {
+ TransformOne(src, dst);
+ if (doTwo)
+ {
+ TransformOne(src.Slice(16), dst.Slice(4));
+ }
+ }
+
+ public static void TransformOne(Span src, Span dst)
+ {
+ var tmp = new int[4 * 4];
+ int tmpOffset = 0;
+ int srcOffset = 0;
+ for (int i = 0; i < 4; ++i)
+ {
+ // vertical pass
+ int a = src[srcOffset] + src[srcOffset + 8];
+ int b = src[srcOffset] - src[srcOffset + 8];
+ int c = Mul2(src[srcOffset + 4]) - Mul1(src[srcOffset + 12]);
+ int d = Mul1(src[srcOffset + 4]) + Mul2(src[srcOffset + 12]);
+ tmp[tmpOffset] = a + d;
+ tmp[tmpOffset + 1] = b + c;
+ tmp[tmpOffset + 2] = b - c;
+ tmp[tmpOffset + 3] = a - d;
+ tmpOffset += 4;
+ srcOffset++;
+ }
+
+ // Each pass is expanding the dynamic range by ~3.85 (upper bound).
+ // The exact value is (2. + (20091 + 35468) / 65536).
+ // After the second pass, maximum interval is [-3794, 3794], assuming
+ // an input in [-2048, 2047] interval. We then need to add a dst value in the [0, 255] range.
+ // In the worst case scenario, the input to clip_8b() can be as large as [-60713, 60968].
+ tmpOffset = 0;
+ for (int i = 0; i < 4; ++i)
+ {
+ // horizontal pass
+ int dc = tmp[tmpOffset] + 4;
+ int a = dc + tmp[tmpOffset + 8];
+ int b = dc - tmp[tmpOffset + 8];
+ int c = Mul2(tmp[tmpOffset + 4]) - Mul1(tmp[tmpOffset + 12]);
+ int d = Mul1(tmp[tmpOffset + 4]) + Mul2(tmp[tmpOffset + 12]);
+ Store(dst, 0, 0, a + d);
+ Store(dst, 1, 0, b + c);
+ Store(dst, 2, 0, b - c);
+ Store(dst, 3, 0, a - d);
+ tmpOffset++;
+ dst = dst.Slice(WebPConstants.Bps);
+ }
+ }
+
+ public static void TransformDc(Span src, Span dst)
+ {
+ int dc = src[0] + 4;
+ for (int j = 0; j < 4; ++j)
+ {
+ for (int i = 0; i < 4; ++i)
+ {
+ Store(dst, i, j, dc);
+ }
+ }
+ }
+
+ // Simplified transform when only src[0], src[1] and src[4] are non-zero
+ public static void TransformAc3(Span src, Span dst)
+ {
+ int a = src[0] + 4;
+ int c4 = Mul2(src[4]);
+ int d4 = Mul1(src[4]);
+ int c1 = Mul2(src[1]);
+ int d1 = Mul1(src[1]);
+ Store2(dst, 0, a + d4, d1, c1);
+ Store2(dst, 1, a + c4, d1, c1);
+ Store2(dst, 2, a - c4, d1, c1);
+ Store2(dst, 3, a - d4, d1, c1);
+ }
+
+ public static void TransformUv(Span src, Span dst)
+ {
+ Transform(src.Slice(0 * 16), dst, true);
+ Transform(src.Slice(2 * 16), dst.Slice(4 * WebPConstants.Bps), true);
+ }
+
+ public static void TransformDcuv(Span src, Span dst)
+ {
+ if (src[0 * 16] != 0)
+ {
+ TransformDc(src.Slice(0 * 16), dst);
+ }
+
+ if (src[1 * 16] != 0)
+ {
+ TransformDc(src.Slice(1 * 16), dst.Slice(4));
+ }
+
+ if (src[2 * 16] != 0)
+ {
+ TransformDc(src.Slice(2 * 16), dst.Slice(4 * WebPConstants.Bps));
+ }
+
+ if (src[3 * 16] != 0)
+ {
+ TransformDc(src.Slice(3 * 16), dst.Slice((4 * WebPConstants.Bps) + 4));
+ }
+ }
+
+ private static void TrueMotion(Span dst, Span yuv, int offset, int size)
+ {
+ // For information about how true motion works, see rfc6386, page 52. ff and section 20.14.
+ int topOffset = offset - WebPConstants.Bps;
+ Span top = yuv.Slice(topOffset);
+ byte p = yuv[topOffset - 1];
+ int leftOffset = offset - 1;
+ byte left = yuv[leftOffset];
+ for (int y = 0; y < size; ++y)
+ {
+ for (int x = 0; x < size; ++x)
+ {
+ dst[x] = (byte)Clamp255(left + top[x] - p);
+ }
+
+ leftOffset += WebPConstants.Bps;
+ left = yuv[leftOffset];
+ dst = dst.Slice(WebPConstants.Bps);
+ }
+ }
+
+ // Simple In-loop filtering (Paragraph 15.2)
+ public static void SimpleVFilter16(Span p, int offset, int stride, int thresh)
+ {
+ int thresh2 = (2 * thresh) + 1;
+ for (int i = 0; i < 16; ++i)
+ {
+ if (NeedsFilter(p, offset + i, stride, thresh2))
+ {
+ DoFilter2(p, offset + i, stride);
+ }
+ }
+ }
+
+ public static void SimpleHFilter16(Span p, int offset, int stride, int thresh)
+ {
+ int thresh2 = (2 * thresh) + 1;
+ for (int i = 0; i < 16; ++i)
+ {
+ if (NeedsFilter(p, offset + (i * stride), 1, thresh2))
+ {
+ DoFilter2(p, offset + (i * stride), 1);
+ }
+ }
+ }
+
+ public static void SimpleVFilter16i(Span p, int offset, int stride, int thresh)
+ {
+ for (int k = 3; k > 0; --k)
+ {
+ offset += 4 * stride;
+ SimpleVFilter16(p, offset, stride, thresh);
+ }
+ }
+
+ public static void SimpleHFilter16i(Span p, int offset, int stride, int thresh)
+ {
+ for (int k = 3; k > 0; --k)
+ {
+ offset += 4;
+ SimpleHFilter16(p, offset, stride, thresh);
+ }
+ }
+
+ public static void VFilter16(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh)
+ {
+ FilterLoop26(p, offset, stride, 1, 16, thresh, ithresh, hevThresh);
+ }
+
+ public static void HFilter16(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh)
+ {
+ FilterLoop26(p, offset, 1, stride, 16, thresh, ithresh, hevThresh);
+ }
+
+ public static void VFilter16i(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh)
+ {
+ for (int k = 3; k > 0; --k)
+ {
+ offset += 4 * stride;
+ FilterLoop24(p, offset, stride, 1, 16, thresh, ithresh, hevThresh);
+ }
+ }
+
+ public static void HFilter16i(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh)
+ {
+ for (int k = 3; k > 0; --k)
+ {
+ offset += 4;
+ FilterLoop24(p, offset, 1, stride, 16, thresh, ithresh, hevThresh);
+ }
+ }
+
+ // 8-pixels wide variant, for chroma filtering.
+ public static void VFilter8(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh)
+ {
+ FilterLoop26(u, offset, stride, 1, 8, thresh, ithresh, hevThresh);
+ FilterLoop26(v, offset, stride, 1, 8, thresh, ithresh, hevThresh);
+ }
+
+ public static void HFilter8(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh)
+ {
+ FilterLoop26(u, offset, 1, stride, 8, thresh, ithresh, hevThresh);
+ FilterLoop26(v, offset, 1, stride, 8, thresh, ithresh, hevThresh);
+ }
+
+ public static void VFilter8i(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh)
+ {
+ FilterLoop24(u, offset + (4 * stride), stride, 1, 8, thresh, ithresh, hevThresh);
+ FilterLoop24(v, offset + (4 * stride), stride, 1, 8, thresh, ithresh, hevThresh);
+ }
+
+ public static void HFilter8i(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh)
+ {
+ FilterLoop24(u, offset + 4, 1, stride, 8, thresh, ithresh, hevThresh);
+ FilterLoop24(v, offset + 4, 1, stride, 8, thresh, ithresh, hevThresh);
+ }
+
+ public static uint LoadUv(byte u, byte v)
+ {
+ // We process u and v together stashed into 32bit(16bit each).
+ return (uint)(u | (v << 16));
+ }
+
+ public static void YuvToBgr(int y, int u, int v, Span bgr)
+ {
+ bgr[0] = (byte)YuvToB(y, u);
+ bgr[1] = (byte)YuvToG(y, u, v);
+ bgr[2] = (byte)YuvToR(y, v);
+ }
+
+ public static int YuvToR(int y, int v)
+ {
+ return Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234);
+ }
+
+ public static int YuvToG(int y, int u, int v)
+ {
+ return Clip8(MultHi(y, 19077) - MultHi(u, 6419) - MultHi(v, 13320) + 8708);
+ }
+
+ public static int YuvToB(int y, int u)
+ {
+ return Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685);
+ }
+
+ // Complex In-loop filtering (Paragraph 15.3)
+ private static void FilterLoop24(
+ Span p,
+ int offset,
+ int hStride,
+ int vStride,
+ int size,
+ int thresh,
+ int ithresh,
+ int hevThresh)
+ {
+ int thresh2 = (2 * thresh) + 1;
+ while (size-- > 0)
+ {
+ if (NeedsFilter2(p, offset, hStride, thresh2, ithresh))
+ {
+ if (Hev(p, offset, hStride, hevThresh))
+ {
+ DoFilter2(p, offset, hStride);
+ }
+ else
+ {
+ DoFilter4(p, offset, hStride);
+ }
+ }
+
+ offset += vStride;
+ }
+ }
+
+ private static void FilterLoop26(
+ Span p,
+ int offset,
+ int hStride,
+ int vStride,
+ int size,
+ int thresh,
+ int ithresh,
+ int hevThresh)
+ {
+ int thresh2 = (2 * thresh) + 1;
+ while (size-- > 0)
+ {
+ if (NeedsFilter2(p, offset, hStride, thresh2, ithresh))
+ {
+ if (Hev(p, offset, hStride, hevThresh))
+ {
+ DoFilter2(p, offset, hStride);
+ }
+ else
+ {
+ DoFilter6(p, offset, hStride);
+ }
+ }
+
+ offset += vStride;
+ }
+ }
+
+ private static void DoFilter2(Span p, int offset, int step)
+ {
+ // 4 pixels in, 2 pixels out.
+ int p1 = p[offset - (2 * step)];
+ int p0 = p[offset - step];
+ int q0 = p[offset];
+ int q1 = p[offset + step];
+ int a = (3 * (q0 - p0)) + WebPLookupTables.Sclip1[p1 - q1];
+ int a1 = WebPLookupTables.Sclip2[(a + 4) >> 3];
+ int a2 = WebPLookupTables.Sclip2[(a + 3) >> 3];
+ p[offset - step] = WebPLookupTables.Clip1[p0 + a2];
+ p[offset] = WebPLookupTables.Clip1[q0 - a1];
+ }
+
+ private static void DoFilter4(Span p, int offset, int step)
+ {
+ // 4 pixels in, 4 pixels out.
+ int p1 = p[offset - (2 * step)];
+ int p0 = p[offset - step];
+ int q0 = p[offset];
+ int q1 = p[offset + step];
+ int a = 3 * (q0 - p0);
+ int a1 = WebPLookupTables.Sclip2[(a + 4) >> 3];
+ int a2 = WebPLookupTables.Sclip2[(a + 3) >> 3];
+ int a3 = (a1 + 1) >> 1;
+ p[offset - (2 * step)] = WebPLookupTables.Clip1[p1 + a3];
+ p[offset - step] = WebPLookupTables.Clip1[p0 + a2];
+ p[offset] = WebPLookupTables.Clip1[q0 - a1];
+ p[offset + step] = WebPLookupTables.Clip1[q1 - a3];
+ }
+
+ private static void DoFilter6(Span p, int offset, int step)
+ {
+ // 6 pixels in, 6 pixels out.
+ int p2 = p[offset - (3 * step)];
+ int p1 = p[offset - (2 * step)];
+ int p0 = p[offset - step];
+ int q0 = p[offset];
+ int q1 = p[offset + step];
+ int q2 = p[offset + (2 * step)];
+ int a = WebPLookupTables.Sclip1[(3 * (q0 - p0)) + WebPLookupTables.Sclip1[p1 - q1]];
+
+ // a is in [-128,127], a1 in [-27,27], a2 in [-18,18] and a3 in [-9,9]
+ int a1 = ((27 * a) + 63) >> 7; // eq. to ((3 * a + 7) * 9) >> 7
+ int a2 = ((18 * a) + 63) >> 7; // eq. to ((2 * a + 7) * 9) >> 7
+ int a3 = ((9 * a) + 63) >> 7; // eq. to ((1 * a + 7) * 9) >> 7
+ p[offset - (3 * step)] = WebPLookupTables.Clip1[p2 + a3];
+ p[offset - (2 * step)] = WebPLookupTables.Clip1[p1 + a2];
+ p[offset - step] = WebPLookupTables.Clip1[p0 + a1];
+ p[offset] = WebPLookupTables.Clip1[q0 - a1];
+ p[offset + step] = WebPLookupTables.Clip1[q1 - a2];
+ p[offset + (2 * step)] = WebPLookupTables.Clip1[q2 - a3];
+ }
+
+ private static bool NeedsFilter(Span p, int offset, int step, int t)
+ {
+ int p1 = p[offset + (-2 * step)];
+ int p0 = p[offset - step];
+ int q0 = p[offset];
+ int q1 = p[offset + step];
+ return ((4 * WebPLookupTables.Abs0[p0 - q0]) + WebPLookupTables.Abs0[p1 - q1]) <= t;
+ }
+
+ private static bool NeedsFilter2(Span p, int offset, int step, int t, int it)
+ {
+ int p3 = p[offset - (4 * step)];
+ int p2 = p[offset - (3 * step)];
+ int p1 = p[offset - (2 * step)];
+ int p0 = p[offset - step];
+ int q0 = p[offset];
+ int q1 = p[offset + step];
+ int q2 = p[offset + (2 * step)];
+ int q3 = p[offset + (3 * step)];
+ if (((4 * WebPLookupTables.Abs0[p0 - q0]) + WebPLookupTables.Abs0[p1 - q1]) > t)
+ {
+ return false;
+ }
+
+ return WebPLookupTables.Abs0[p3 - p2] <= it && WebPLookupTables.Abs0[p2 - p1] <= it &&
+ WebPLookupTables.Abs0[p1 - p0] <= it && WebPLookupTables.Abs0[q3 - q2] <= it &&
+ WebPLookupTables.Abs0[q2 - q1] <= it && WebPLookupTables.Abs0[q1 - q0] <= it;
+ }
+
+ private static bool Hev(Span p, int offset, int step, int thresh)
+ {
+ int p1 = p[offset - (2 * step)];
+ int p0 = p[offset - step];
+ int q0 = p[offset];
+ int q1 = p[offset + step];
+ return (WebPLookupTables.Abs0[p1 - p0] > thresh) || (WebPLookupTables.Abs0[q1 - q0] > thresh);
+ }
+
+ private static int MultHi(int v, int coeff)
+ {
+ return (v * coeff) >> 8;
+ }
+
+ private static void Store(Span dst, int x, int y, int v)
+ {
+ dst[x + (y * WebPConstants.Bps)] = Clip8B(dst[x + (y * WebPConstants.Bps)] + (v >> 3));
+ }
+
+ private static void Store2(Span dst, int y, int dc, int d, int c)
+ {
+ Store(dst, 0, y, dc + d);
+ Store(dst, 1, y, dc + c);
+ Store(dst, 2, y, dc - c);
+ Store(dst, 3, y, dc - d);
+ }
+
+ private static int Mul1(int a)
+ {
+ return ((a * 20091) >> 16) + a;
+ }
+
+ private static int Mul2(int a)
+ {
+ return (a * 35468) >> 16;
+ }
+
+ private static byte Clip8B(int v)
+ {
+ return (byte)((v & ~0xff) is 0 ? v : (v < 0) ? 0 : 255);
+ }
+
+ private static byte Clip8(int v)
+ {
+ int yuvMask = (256 << 6) - 1;
+ return (byte)(((v & ~yuvMask) is 0) ? (v >> 6) : (v < 0) ? 0 : 255);
+ }
+
+ private static void Put8x8uv(byte value, Span dst)
+ {
+ for (int j = 0; j < 8; ++j)
+ {
+ // memset(dst + j * BPS, value, 8);
+ Memset(dst, value, j * WebPConstants.Bps, 8);
+ }
+ }
+
+ private static void Memset(Span dst, byte value, int startIdx, int count)
+ {
+ for (int i = 0; i < count; i++)
+ {
+ dst[startIdx + i] = value;
+ }
+ }
+
+ private static byte Avg2(byte a, byte b)
+ {
+ return (byte)((a + b + 1) >> 1);
+ }
+
+ private static byte Avg3(byte a, byte b, byte c)
+ {
+ return (byte)((a + (2 * b) + c + 2) >> 2);
+ }
+
+ private static void Dst(Span dst, int x, int y, byte v)
+ {
+ dst[x + (y * WebPConstants.Bps)] = v;
+ }
+
+ private static int Clamp255(int x)
+ {
+ return x < 0 ? 0 : (x > 255 ? 255 : x);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Readme.md b/src/ImageSharp/Formats/WebP/Readme.md
new file mode 100644
index 0000000000..c4c8004644
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Readme.md
@@ -0,0 +1,9 @@
+# WebP Format
+
+Reference implementation, specification and stuff like that:
+
+- [google webp introduction](https://developers.google.com/speed/webp)
+- [WebP Spec 1.0.3](https://chromium.googlesource.com/webm/libwebp/+/v1.0.3/doc/webp-container-spec.txt)
+- [WebP VP8 chunk Spec](http://tools.ietf.org/html/rfc6386)
+- [WebP filefront](https://wiki.fileformat.com/image/webp/)
+- [WebP test data](https://github.com/webmproject/libwebp-test-data/)
diff --git a/src/ImageSharp/Formats/WebP/ReconstructionFilter.cs b/src/ImageSharp/Formats/WebP/ReconstructionFilter.cs
new file mode 100644
index 0000000000..ff59e6b668
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/ReconstructionFilter.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ internal enum ReconstructionFilter
+ {
+ None,
+ Bicubic,
+ Bilinear
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/VP8BandProbas.cs b/src/ImageSharp/Formats/WebP/VP8BandProbas.cs
new file mode 100644
index 0000000000..a9c9420d16
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/VP8BandProbas.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// All the probabilities associated to one band.
+ ///
+ internal class Vp8BandProbas
+ {
+ public Vp8BandProbas()
+ {
+ this.Probabilities = new Vp8ProbaArray[WebPConstants.NumCtx];
+ for (int i = 0; i < WebPConstants.NumCtx; i++)
+ {
+ this.Probabilities[i] = new Vp8ProbaArray();
+ }
+ }
+
+ public Vp8ProbaArray[] Probabilities { get; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8BitReader.cs b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs
new file mode 100644
index 0000000000..365ab3bddd
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8BitReader.cs
@@ -0,0 +1,239 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers.Binary;
+using System.IO;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// A bit reader for VP8 streams.
+ ///
+ internal class Vp8BitReader : BitReaderBase
+ {
+ private const int BitsCount = 56;
+
+ ///
+ /// Current value.
+ ///
+ private ulong value;
+
+ ///
+ /// Current range minus 1. In [127, 254] interval.
+ ///
+ private uint range;
+
+ ///
+ /// Number of valid bits left.
+ ///
+ private int bits;
+
+ ///
+ /// Max packed-read position of the buffer.
+ ///
+ private uint bufferMax;
+
+ private uint bufferEnd;
+
+ ///
+ /// True if input is exhausted.
+ ///
+ private bool eof;
+
+ ///
+ /// Byte position in buffer.
+ ///
+ private long pos;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The input stream to read from.
+ /// The raw image data size in bytes.
+ /// Used for allocating memory during reading data from the stream.
+ /// The partition length.
+ /// Start index in the data array. Defaults to 0.
+ public Vp8BitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator, uint partitionLength, int startPos = 0)
+ {
+ this.ImageDataSize = imageDataSize;
+ this.PartitionLength = partitionLength;
+ this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator);
+ this.InitBitreader(partitionLength, startPos);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The raw encoded image data.
+ /// The partition length.
+ /// Start index in the data array. Defaults to 0.
+ public Vp8BitReader(byte[] imageData, uint partitionLength, int startPos = 0)
+ {
+ this.Data = imageData;
+ this.ImageDataSize = (uint)imageData.Length;
+ this.PartitionLength = partitionLength;
+ this.InitBitreader(partitionLength, startPos);
+ }
+
+ public int Pos
+ {
+ get { return (int)this.pos; }
+ }
+
+ public uint ImageDataSize { get; }
+
+ public uint PartitionLength { get; }
+
+ public uint Remaining { get; set; }
+
+ public int GetBit(int prob)
+ {
+ uint range = this.range;
+ if (this.bits < 0)
+ {
+ this.LoadNewBytes();
+ }
+
+ int pos = this.bits;
+ uint split = (uint)((range * prob) >> 8);
+ ulong value = this.value >> pos;
+ bool bit = value > split;
+ if (bit)
+ {
+ range -= split;
+ this.value -= (ulong)(split + 1) << pos;
+ }
+ else
+ {
+ range = split + 1;
+ }
+
+ int shift = 7 ^ this.BitsLog2Floor(range);
+ range <<= shift;
+ this.bits -= shift;
+
+ this.range = range - 1;
+
+ return bit ? 1 : 0;
+ }
+
+ // Simplified version of VP8GetBit() for prob=0x80 (note shift is always 1 here)
+ public int GetSigned(int v)
+ {
+ if (this.bits < 0)
+ {
+ this.LoadNewBytes();
+ }
+
+ int pos = this.bits;
+ uint split = this.range >> 1;
+ ulong value = this.value >> pos;
+ ulong mask = (split - value) >> 31; // -1 or 0
+ this.bits -= 1;
+ this.range += (uint)mask;
+ this.range |= 1;
+ this.value -= ((split + 1) & mask) << pos;
+
+ return (v ^ (int)mask) - (int)mask;
+ }
+
+ public bool ReadBool()
+ {
+ return this.ReadValue(1) is 1;
+ }
+
+ public uint ReadValue(int nBits)
+ {
+ Guard.MustBeGreaterThan(nBits, 0, nameof(nBits));
+
+ uint v = 0;
+ while (nBits-- > 0)
+ {
+ v |= (uint)this.GetBit(0x80) << nBits;
+ }
+
+ return v;
+ }
+
+ public int ReadSignedValue(int nBits)
+ {
+ Guard.MustBeGreaterThan(nBits, 0, nameof(nBits));
+
+ int value = (int)this.ReadValue(nBits);
+ return this.ReadValue(1) != 0 ? -value : value;
+ }
+
+ private void InitBitreader(uint size, int pos = 0)
+ {
+ this.range = 255 - 1;
+ this.value = 0;
+ this.bits = -8; // to load the very first 8 bits.
+ this.eof = false;
+ this.pos = pos;
+ this.bufferEnd = (uint)(pos + size);
+ this.bufferMax = (uint)(size > 8 ? pos + size - 8 + 1 : pos);
+
+ this.LoadNewBytes();
+ }
+
+ private void LoadNewBytes()
+ {
+ if (this.pos < this.bufferMax)
+ {
+ ulong inBits = BinaryPrimitives.ReadUInt64LittleEndian(this.Data.AsSpan((int)this.pos, 8));
+ this.pos += BitsCount >> 3;
+ ulong bits = this.ByteSwap64(inBits);
+ bits >>= 64 - BitsCount;
+ this.value = bits | (this.value << BitsCount);
+ this.bits += BitsCount;
+ }
+ else
+ {
+ this.LoadFinalBytes();
+ }
+ }
+
+ private void LoadFinalBytes()
+ {
+ // Only read 8bits at a time.
+ if (this.pos < this.bufferEnd)
+ {
+ this.bits += 8;
+ this.value = this.Data[this.pos++] | (this.value << 8);
+ }
+ else if (!this.eof)
+ {
+ this.value <<= 8;
+ this.bits += 8;
+ this.eof = true;
+ }
+ else
+ {
+ this.bits = 0; // This is to avoid undefined behaviour with shifts.
+ }
+ }
+
+ private ulong ByteSwap64(ulong x)
+ {
+ x = ((x & 0xffffffff00000000ul) >> 32) | ((x & 0x00000000fffffffful) << 32);
+ x = ((x & 0xffff0000ffff0000ul) >> 16) | ((x & 0x0000ffff0000fffful) << 16);
+ x = ((x & 0xff00ff00ff00ff00ul) >> 8) | ((x & 0x00ff00ff00ff00fful) << 8);
+ return x;
+ }
+
+ // Returns 31 ^ clz(n) = log2(n).Returns 31 ^ clz(n) = log2(n).
+ private int BitsLog2Floor(uint n)
+ {
+ int logValue = 0;
+ while (n >= 256)
+ {
+ logValue += 8;
+ n >>= 8;
+ }
+
+ return logValue + WebPLookupTables.LogTable8bit[n];
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8Decoder.cs b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs
new file mode 100644
index 0000000000..f47100ef3a
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8Decoder.cs
@@ -0,0 +1,361 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Holds information for decoding a lossy webp image.
+ ///
+ internal class Vp8Decoder : IDisposable
+ {
+ private Vp8MacroBlock leftMacroBlock;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The frame header.
+ /// The picture header.
+ /// The segment header.
+ /// The probabilities.
+ /// Used for allocating memory for the pixel data output and the temporary buffers.
+ public Vp8Decoder(Vp8FrameHeader frameHeader, Vp8PictureHeader pictureHeader, Vp8SegmentHeader segmentHeader, Vp8Proba probabilities, MemoryAllocator memoryAllocator)
+ {
+ this.FilterHeader = new Vp8FilterHeader();
+ this.FrameHeader = frameHeader;
+ this.PictureHeader = pictureHeader;
+ this.SegmentHeader = segmentHeader;
+ this.Probabilities = probabilities;
+ this.IntraL = new byte[4];
+ this.MbWidth = (int)((this.PictureHeader.Width + 15) >> 4);
+ this.MbHeight = (int)((this.PictureHeader.Height + 15) >> 4);
+ this.CacheYStride = 16 * this.MbWidth;
+ this.CacheUvStride = 8 * this.MbWidth;
+ this.MacroBlockInfo = new Vp8MacroBlock[this.MbWidth + 1];
+ this.MacroBlockData = new Vp8MacroBlockData[this.MbWidth];
+ this.YuvTopSamples = new Vp8TopSamples[this.MbWidth];
+ this.FilterInfo = new Vp8FilterInfo[this.MbWidth];
+ for (int i = 0; i < this.MbWidth; i++)
+ {
+ this.MacroBlockInfo[i] = new Vp8MacroBlock();
+ this.MacroBlockData[i] = new Vp8MacroBlockData();
+ this.YuvTopSamples[i] = new Vp8TopSamples();
+ this.FilterInfo[i] = new Vp8FilterInfo();
+ }
+
+ this.MacroBlockInfo[this.MbWidth] = new Vp8MacroBlock();
+
+ this.DeQuantMatrices = new Vp8QuantMatrix[WebPConstants.NumMbSegments];
+ this.FilterStrength = new Vp8FilterInfo[WebPConstants.NumMbSegments, 2];
+ for (int i = 0; i < WebPConstants.NumMbSegments; i++)
+ {
+ this.DeQuantMatrices[i] = new Vp8QuantMatrix();
+ for (int j = 0; j < 2; j++)
+ {
+ this.FilterStrength[i, j] = new Vp8FilterInfo();
+ }
+ }
+
+ uint width = pictureHeader.Width;
+ uint height = pictureHeader.Height;
+
+ int extraRows = WebPConstants.FilterExtraRows[(int)LoopFilter.Complex]; // assuming worst case: complex filter
+ int extraY = extraRows * this.CacheYStride;
+ int extraUv = (extraRows / 2) * this.CacheUvStride;
+ this.YuvBuffer = memoryAllocator.Allocate((WebPConstants.Bps * 17) + (WebPConstants.Bps * 9) + extraY);
+ this.CacheY = memoryAllocator.Allocate((16 * this.CacheYStride) + extraY);
+ this.CacheU = memoryAllocator.Allocate((16 * this.CacheUvStride) + extraUv);
+ this.CacheV = memoryAllocator.Allocate((16 * this.CacheUvStride) + extraUv);
+ this.TmpYBuffer = memoryAllocator.Allocate((int)width);
+ this.TmpUBuffer = memoryAllocator.Allocate((int)width);
+ this.TmpVBuffer = memoryAllocator.Allocate((int)width);
+ this.Pixels = memoryAllocator.Allocate((int)(width * height * 4));
+
+ this.YuvBuffer.Memory.Span.Fill(205);
+ this.CacheY.Memory.Span.Fill(205);
+ this.CacheU.Memory.Span.Fill(205);
+ this.CacheV.Memory.Span.Fill(205);
+
+ this.Vp8BitReaders = new Vp8BitReader[WebPConstants.MaxNumPartitions];
+ }
+
+ ///
+ /// Gets the frame header.
+ ///
+ public Vp8FrameHeader FrameHeader { get; }
+
+ ///
+ /// Gets the picture header.
+ ///
+ public Vp8PictureHeader PictureHeader { get; }
+
+ ///
+ /// Gets the filter header.
+ ///
+ public Vp8FilterHeader FilterHeader { get; }
+
+ ///
+ /// Gets the segment header.
+ ///
+ public Vp8SegmentHeader SegmentHeader { get; }
+
+ ///
+ /// Gets or sets the number of partitions minus one.
+ ///
+ public int NumPartsMinusOne { get; set; }
+
+ ///
+ /// Gets the per-partition boolean decoders.
+ ///
+ public Vp8BitReader[] Vp8BitReaders { get; }
+
+ ///
+ /// Gets the dequantization matrices (one set of DC/AC dequant factor per segment).
+ ///
+ public Vp8QuantMatrix[] DeQuantMatrices { get; }
+
+ ///
+ /// Gets or sets a value indicating whether to use the skip probabilities.
+ ///
+ public bool UseSkipProbability { get; set; }
+
+ ///
+ /// Gets or sets the skip probability.
+ ///
+ public byte SkipProbability { get; set; }
+
+ ///
+ /// Gets or sets the Probabilities.
+ ///
+ public Vp8Proba Probabilities { get; set; }
+
+ ///
+ /// Gets or sets the top intra modes values: 4 * MbWidth.
+ ///
+ public byte[] IntraT { get; set; }
+
+ ///
+ /// Gets the left intra modes values.
+ ///
+ public byte[] IntraL { get; }
+
+ ///
+ /// Gets the width in macroblock units.
+ ///
+ public int MbWidth { get; }
+
+ ///
+ /// Gets the height in macroblock units.
+ ///
+ public int MbHeight { get; }
+
+ ///
+ /// Gets or sets the top-left x index of the macroblock that must be in-loop filtered.
+ ///
+ public int TopLeftMbX { get; set; }
+
+ ///
+ /// Gets or sets the top-left y index of the macroblock that must be in-loop filtered.
+ ///
+ public int TopLeftMbY { get; set; }
+
+ ///
+ /// Gets or sets the last bottom-right x index of the macroblock that must be decoded.
+ ///
+ public int BottomRightMbX { get; set; }
+
+ ///
+ /// Gets or sets the last bottom-right y index of the macroblock that must be decoded.
+ ///
+ public int BottomRightMbY { get; set; }
+
+ ///
+ /// Gets or sets the current x position in macroblock units.
+ ///
+ public int MbX { get; set; }
+
+ ///
+ /// Gets or sets the current y position in macroblock units.
+ ///
+ public int MbY { get; set; }
+
+ ///
+ /// Gets the parsed reconstruction data.
+ ///
+ public Vp8MacroBlockData[] MacroBlockData { get; }
+
+ ///
+ /// Gets the contextual macroblock info.
+ ///
+ public Vp8MacroBlock[] MacroBlockInfo { get; }
+
+ ///
+ /// Gets or sets the loop filter used. The purpose of the loop filter is to eliminate (or at least reduce)
+ /// visually objectionable artifacts.
+ ///
+ public LoopFilter Filter { get; set; }
+
+ ///
+ /// Gets the filter strengths.
+ ///
+ public Vp8FilterInfo[,] FilterStrength { get; }
+
+ public IMemoryOwner YuvBuffer { get; }
+
+ public Vp8TopSamples[] YuvTopSamples { get; }
+
+ public IMemoryOwner CacheY { get; }
+
+ public IMemoryOwner CacheU { get; }
+
+ public IMemoryOwner CacheV { get; }
+
+ public int CacheYOffset { get; set; }
+
+ public int CacheUvOffset { get; set; }
+
+ public int CacheYStride { get; }
+
+ public int CacheUvStride { get; }
+
+ public IMemoryOwner TmpYBuffer { get; }
+
+ public IMemoryOwner TmpUBuffer { get; }
+
+ public IMemoryOwner TmpVBuffer { get; }
+
+ ///
+ /// Gets the pixel buffer where the decoded pixel data will be stored.
+ ///
+ public IMemoryOwner Pixels { get; }
+
+ ///
+ /// Gets or sets filter strength info.
+ ///
+ public Vp8FilterInfo[] FilterInfo { get; set; }
+
+ public Vp8MacroBlock CurrentMacroBlock
+ {
+ get
+ {
+ return this.MacroBlockInfo[this.MbX];
+ }
+ }
+
+ public Vp8MacroBlock LeftMacroBlock
+ {
+ get
+ {
+ if (this.leftMacroBlock is null)
+ {
+ this.leftMacroBlock = new Vp8MacroBlock();
+ }
+
+ return this.leftMacroBlock;
+ }
+ }
+
+ public Vp8MacroBlockData CurrentBlockData
+ {
+ get
+ {
+ return this.MacroBlockData[this.MbX];
+ }
+ }
+
+ public void PrecomputeFilterStrengths()
+ {
+ if (this.Filter is LoopFilter.None)
+ {
+ return;
+ }
+
+ Vp8FilterHeader hdr = this.FilterHeader;
+ for (int s = 0; s < WebPConstants.NumMbSegments; ++s)
+ {
+ int baseLevel;
+
+ // First, compute the initial level.
+ if (this.SegmentHeader.UseSegment)
+ {
+ baseLevel = this.SegmentHeader.FilterStrength[s];
+ if (!this.SegmentHeader.Delta)
+ {
+ baseLevel += hdr.Level;
+ }
+ }
+ else
+ {
+ baseLevel = hdr.Level;
+ }
+
+ for (int i4x4 = 0; i4x4 <= 1; ++i4x4)
+ {
+ Vp8FilterInfo info = this.FilterStrength[s, i4x4];
+ int level = baseLevel;
+ if (hdr.UseLfDelta)
+ {
+ level += hdr.RefLfDelta[0];
+ if (i4x4 > 0)
+ {
+ level += hdr.ModeLfDelta[0];
+ }
+ }
+
+ level = (level < 0) ? 0 : (level > 63) ? 63 : level;
+ if (level > 0)
+ {
+ int iLevel = level;
+ if (hdr.Sharpness > 0)
+ {
+ if (hdr.Sharpness > 4)
+ {
+ iLevel >>= 2;
+ }
+ else
+ {
+ iLevel >>= 1;
+ }
+
+ if (iLevel > 9 - hdr.Sharpness)
+ {
+ iLevel = 9 - hdr.Sharpness;
+ }
+ }
+
+ if (iLevel < 1)
+ {
+ iLevel = 1;
+ }
+
+ info.InnerLevel = (byte)iLevel;
+ info.Limit = (byte)((2 * level) + iLevel);
+ info.HighEdgeVarianceThreshold = (byte)((level >= 40) ? 2 : (level >= 15) ? 1 : 0);
+ }
+ else
+ {
+ info.Limit = 0; // no filtering.
+ }
+
+ info.UseInnerFiltering = (byte)i4x4;
+ }
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ this.YuvBuffer.Dispose();
+ this.CacheY.Dispose();
+ this.CacheU.Dispose();
+ this.CacheV.Dispose();
+ this.TmpYBuffer.Dispose();
+ this.TmpUBuffer.Dispose();
+ this.TmpVBuffer.Dispose();
+ this.Pixels.Dispose();
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8FilterHeader.cs b/src/ImageSharp/Formats/WebP/Vp8FilterHeader.cs
new file mode 100644
index 0000000000..7093b1bf0a
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8FilterHeader.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ internal class Vp8FilterHeader
+ {
+ private const int NumRefLfDeltas = 4;
+
+ private const int NumModeLfDeltas = 4;
+
+ public Vp8FilterHeader()
+ {
+ this.RefLfDelta = new int[NumRefLfDeltas];
+ this.ModeLfDelta = new int[NumModeLfDeltas];
+ }
+
+ ///
+ /// Gets or sets the loop filter.
+ ///
+ public LoopFilter LoopFilter { get; set; }
+
+ // [0..63]
+ public int Level { get; set; }
+
+ // [0..7]
+ public int Sharpness { get; set; }
+
+ public bool UseLfDelta { get; set; }
+
+ public int[] RefLfDelta { get; private set; }
+
+ public int[] ModeLfDelta { get; private set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs b/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs
new file mode 100644
index 0000000000..760e1b5c55
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8FilterInfo.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Filter information.
+ ///
+ internal class Vp8FilterInfo : IDeepCloneable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Vp8FilterInfo()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The filter info to create an instance from.
+ public Vp8FilterInfo(Vp8FilterInfo other)
+ {
+ this.Limit = other.Limit;
+ this.HighEdgeVarianceThreshold = other.HighEdgeVarianceThreshold;
+ this.InnerLevel = other.InnerLevel;
+ this.UseInnerFiltering = other.UseInnerFiltering;
+ }
+
+ ///
+ /// Gets or sets the filter limit in [3..189], or 0 if no filtering.
+ ///
+ public byte Limit { get; set; }
+
+ ///
+ /// Gets or sets the inner limit in [1..63].
+ ///
+ public byte InnerLevel { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether to do inner filtering.
+ /// TODO: can this be a bool?
+ ///
+ public byte UseInnerFiltering { get; set; }
+
+ ///
+ /// Gets or sets the high edge variance threshold in [0..2].
+ ///
+ public byte HighEdgeVarianceThreshold { get; set; }
+
+ ///
+ public IDeepCloneable DeepClone() => new Vp8FilterInfo(this);
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8FrameHeader.cs b/src/ImageSharp/Formats/WebP/Vp8FrameHeader.cs
new file mode 100644
index 0000000000..c7bfa67f65
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8FrameHeader.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Vp8 frame header information.
+ ///
+ internal class Vp8FrameHeader
+ {
+ ///
+ /// Gets or sets a value indicating whether this is a key frame.
+ ///
+ public bool KeyFrame { get; set; }
+
+ ///
+ /// Gets or sets Vp8 profile [0..3].
+ ///
+ public sbyte Profile { get; set; }
+
+ ///
+ /// Gets or sets the partition length.
+ ///
+ public uint PartitionLength { get; set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs b/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs
new file mode 100644
index 0000000000..18e311da3b
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8HeaderType.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Enum for the different VP8 chunk header types.
+ ///
+ public enum Vp8HeaderType
+ {
+ ///
+ /// Invalid VP8 header.
+ ///
+ Invalid = 0,
+
+ ///
+ /// A VP8 header.
+ ///
+ Vp8 = 1,
+
+ ///
+ /// VP8 header, signaling the use of VP8L lossless format.
+ ///
+ Vp8L = 2,
+
+ ///
+ /// Header for a extended-VP8 chunk.
+ ///
+ Vp8X = 3,
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8Io.cs b/src/ImageSharp/Formats/WebP/Vp8Io.cs
new file mode 100644
index 0000000000..feb763129f
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8Io.cs
@@ -0,0 +1,80 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ internal ref struct Vp8Io
+ {
+ ///
+ /// Gets or sets the picture width in pixels (invariable).
+ /// Original, uncropped dimensions.
+ /// The actual area passed to put() is stored in /> field.
+ ///
+ public int Width { get; set; }
+
+ ///
+ /// Gets or sets the picture height in pixels (invariable).
+ /// Original, uncropped dimensions.
+ /// The actual area passed to put() is stored in /> field.
+ ///
+ public int Height { get; set; }
+
+ ///
+ /// Gets or sets the y-position of the current macroblock.
+ ///
+ public int MbY { get; set; }
+
+ ///
+ /// Gets or sets number of columns in the sample.
+ ///
+ public int MbW { get; set; }
+
+ ///
+ /// Gets or sets number of rows in the sample.
+ ///
+ public int MbH { get; set; }
+
+ ///
+ /// Gets or sets the luma component.
+ ///
+ public Span Y { get; set; }
+
+ ///
+ /// Gets or sets the U chroma component.
+ ///
+ public Span U { get; set; }
+
+ ///
+ /// Gets or sets the V chroma component.
+ ///
+ public Span V { get; set; }
+
+ ///
+ /// Gets or sets the row stride for luma.
+ ///
+ public int YStride { get; set; }
+
+ ///
+ /// Gets or sets the row stride for chroma.
+ ///
+ public int UvStride { get; set; }
+
+ public bool UseCropping { get; set; }
+
+ public int CropLeft { get; set; }
+
+ public int CropRight { get; set; }
+
+ public int CropTop { get; set; }
+
+ public int CropBottom { get; set; }
+
+ public bool UseScaling { get; set; }
+
+ public int ScaledWidth { get; set; }
+
+ public int ScaledHeight { get; set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs
new file mode 100644
index 0000000000..6f3e4610af
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8LBitReader.cs
@@ -0,0 +1,219 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// A bit reader for reading lossless webp streams.
+ ///
+ internal class Vp8LBitReader : BitReaderBase
+ {
+ ///
+ /// Maximum number of bits (inclusive) the bit-reader can handle.
+ ///
+ private const int Vp8LMaxNumBitRead = 24;
+
+ ///
+ /// Number of bits prefetched.
+ ///
+ private const int Lbits = 64;
+
+ ///
+ /// Minimum number of bytes ready after VP8LFillBitWindow.
+ ///
+ private const int Wbits = 32;
+
+ private readonly uint[] bitMask =
+ {
+ 0,
+ 0x000001, 0x000003, 0x000007, 0x00000f,
+ 0x00001f, 0x00003f, 0x00007f, 0x0000ff,
+ 0x0001ff, 0x0003ff, 0x0007ff, 0x000fff,
+ 0x001fff, 0x003fff, 0x007fff, 0x00ffff,
+ 0x01ffff, 0x03ffff, 0x07ffff, 0x0fffff,
+ 0x1fffff, 0x3fffff, 0x7fffff, 0xffffff
+ };
+
+ ///
+ /// Pre-fetched bits.
+ ///
+ private ulong value;
+
+ ///
+ /// Buffer length.
+ ///
+ private readonly long len;
+
+ ///
+ /// Byte position in buffer.
+ ///
+ private long pos;
+
+ ///
+ /// Current bit-reading position in value.
+ ///
+ private int bitPos;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Lossless compressed image data.
+ public Vp8LBitReader(byte[] data)
+ {
+ this.Data = data;
+ this.len = data.Length;
+ this.value = 0;
+ this.bitPos = 0;
+ this.Eos = false;
+
+ ulong currentValue = 0;
+ for (int i = 0; i < 8; ++i)
+ {
+ currentValue |= (ulong)this.Data[i] << (8 * i);
+ }
+
+ this.value = currentValue;
+ this.pos = 8;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The input stream to read from.
+ /// The raw image data size in bytes.
+ /// Used for allocating memory during reading data from the stream.
+ public Vp8LBitReader(Stream inputStream, uint imageDataSize, MemoryAllocator memoryAllocator)
+ {
+ long length = imageDataSize;
+
+ this.ReadImageDataFromStream(inputStream, (int)imageDataSize, memoryAllocator);
+
+ this.len = length;
+ this.value = 0;
+ this.bitPos = 0;
+ this.Eos = false;
+
+ if (length > sizeof(long))
+ {
+ length = sizeof(long);
+ }
+
+ ulong currentValue = 0;
+ for (int i = 0; i < length; ++i)
+ {
+ currentValue |= (ulong)this.Data[i] << (8 * i);
+ }
+
+ this.value = currentValue;
+ this.pos = length;
+ }
+
+ ///
+ /// Gets or sets a value indicating whether a bit was read past the end of buffer.
+ ///
+ public bool Eos { get; set; }
+
+ ///
+ /// Reads a unsigned short value from the buffer. The bits of each byte are read in least-significant-bit-first order.
+ ///
+ /// The number of bits to read (should not exceed 16).
+ /// A ushort value.
+ public uint ReadValue(int nBits)
+ {
+ Guard.MustBeGreaterThan(nBits, 0, nameof(nBits));
+
+ if (!this.Eos && nBits <= Vp8LMaxNumBitRead)
+ {
+ ulong val = this.PrefetchBits() & this.bitMask[nBits];
+ int newBits = this.bitPos + nBits;
+ this.bitPos = newBits;
+ this.ShiftBytes();
+ return (uint)val;
+ }
+
+ this.SetEndOfStream();
+ return 0;
+ }
+
+ ///
+ /// Reads a single bit from the stream.
+ ///
+ /// True if the bit read was 1, false otherwise.
+ public bool ReadBit()
+ {
+ uint bit = this.ReadValue(1);
+ return bit != 0;
+ }
+
+ ///
+ /// For jumping over a number of bits in the bit stream when accessed with PrefetchBits and FillBitWindow.
+ ///
+ /// The number of bits to advance the position.
+ public void AdvanceBitPosition(int numberOfBits)
+ {
+ this.bitPos += numberOfBits;
+ }
+
+ ///
+ /// Return the pre-fetched bits, so they can be looked up.
+ ///
+ /// The pre-fetched bits.
+ public ulong PrefetchBits()
+ {
+ return this.value >> (this.bitPos & (Lbits - 1));
+ }
+
+ ///
+ /// Advances the read buffer by 4 bytes to make room for reading next 32 bits.
+ ///
+ public void FillBitWindow()
+ {
+ if (this.bitPos >= Wbits)
+ {
+ this.DoFillBitWindow();
+ }
+ }
+
+ ///
+ /// Returns true if there was an attempt at reading bit past the end of the buffer.
+ ///
+ /// True, if end of buffer was reached.
+ public bool IsEndOfStream()
+ {
+ return this.Eos || ((this.pos == this.len) && (this.bitPos > Lbits));
+ }
+
+ private void DoFillBitWindow()
+ {
+ this.ShiftBytes();
+ }
+
+ ///
+ /// If not at EOS, reload up to Vp8LLbits byte-by-byte.
+ ///
+ private void ShiftBytes()
+ {
+ while (this.bitPos >= 8 && this.pos < this.len)
+ {
+ this.value >>= 8;
+ this.value |= (ulong)this.Data[this.pos] << (Lbits - 8);
+ ++this.pos;
+ this.bitPos -= 8;
+ }
+
+ if (this.IsEndOfStream())
+ {
+ this.SetEndOfStream();
+ }
+ }
+
+ private void SetEndOfStream()
+ {
+ this.Eos = true;
+ this.bitPos = 0; // To avoid undefined behaviour with shifts.
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs
new file mode 100644
index 0000000000..76010fdb22
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8LDecoder.cs
@@ -0,0 +1,70 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Holds information for decoding a lossless webp image.
+ ///
+ internal class Vp8LDecoder : IDisposable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The width of the image.
+ /// The height of the image.
+ /// Used for allocating memory for the pixel data output.
+ public Vp8LDecoder(int width, int height, MemoryAllocator memoryAllocator)
+ {
+ this.Width = width;
+ this.Height = height;
+ this.Metadata = new Vp8LMetadata();
+ this.Pixels = memoryAllocator.Allocate(width * height, AllocationOptions.Clean);
+ }
+
+ ///
+ /// Gets or sets the width of the image to decode.
+ ///
+ public int Width { get; set; }
+
+ ///
+ /// Gets or sets the height of the image to decode.
+ ///
+ public int Height { get; set; }
+
+ ///
+ /// Gets or sets the necessary VP8L metadata (like huffman tables) to decode the image.
+ ///
+ public Vp8LMetadata Metadata { get; set; }
+
+ ///
+ /// Gets or sets the transformations which needs to be reversed.
+ ///
+ public List Transforms { get; set; }
+
+ ///
+ /// Gets the pixel data.
+ ///
+ public IMemoryOwner Pixels { get; }
+
+ ///
+ public void Dispose()
+ {
+ this.Pixels.Dispose();
+ this.Metadata?.HuffmanImage?.Dispose();
+
+ if (this.Transforms != null)
+ {
+ foreach (Vp8LTransform transform in this.Transforms)
+ {
+ transform.Data?.Dispose();
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs b/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs
new file mode 100644
index 0000000000..737def7f41
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8LMetadata.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Buffers;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ internal class Vp8LMetadata
+ {
+ public int ColorCacheSize { get; set; }
+
+ public ColorCache ColorCache { get; set; }
+
+ public int HuffmanMask { get; set; }
+
+ public int HuffmanSubSampleBits { get; set; }
+
+ public int HuffmanXSize { get; set; }
+
+ public IMemoryOwner HuffmanImage { get; set; }
+
+ public int NumHTreeGroups { get; set; }
+
+ public HTreeGroup[] HTreeGroups { get; set; }
+
+ public HuffmanCode[] HuffmanTables { get; set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8LTransform.cs b/src/ImageSharp/Formats/WebP/Vp8LTransform.cs
new file mode 100644
index 0000000000..ae62123d31
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8LTransform.cs
@@ -0,0 +1,47 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Buffers;
+using System.Diagnostics;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Data associated with a VP8L transformation to reduce the entropy.
+ ///
+ [DebuggerDisplay("Transformtype: {TransformType}")]
+ internal class Vp8LTransform
+ {
+ public Vp8LTransform(Vp8LTransformType transformType, int xSize, int ySize)
+ {
+ this.TransformType = transformType;
+ this.XSize = xSize;
+ this.YSize = ySize;
+ }
+
+ ///
+ /// Gets the transform type.
+ ///
+ public Vp8LTransformType TransformType { get; }
+
+ ///
+ /// Gets or sets the subsampling bits defining the transform window.
+ ///
+ public int Bits { get; set; }
+
+ ///
+ /// Gets or sets the transform window X index.
+ ///
+ public int XSize { get; set; }
+
+ ///
+ /// Gets the transform window Y index.
+ ///
+ public int YSize { get; }
+
+ ///
+ /// Gets or sets the transform data.
+ ///
+ public IMemoryOwner Data { get; set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8LTransformType.cs b/src/ImageSharp/Formats/WebP/Vp8LTransformType.cs
new file mode 100644
index 0000000000..7e1be4deb2
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8LTransformType.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Enum for the different transform types. Transformations are reversible manipulations of the image data
+ /// that can reduce the remaining symbolic entropy by modeling spatial and color correlations.
+ /// Transformations can make the final compression more dense.
+ ///
+ public enum Vp8LTransformType : uint
+ {
+ ///
+ /// The predictor transform can be used to reduce entropy by exploiting the fact that neighboring pixels are often correlated.
+ ///
+ PredictorTransform = 0,
+
+ ///
+ /// The goal of the color transform is to decorrelate the R, G and B values of each pixel.
+ /// Color transform keeps the green (G) value as it is, transforms red (R) based on green and transforms blue (B) based on green and then based on red.
+ ///
+ CrossColorTransform = 1,
+
+ ///
+ /// The subtract green transform subtracts green values from red and blue values of each pixel.
+ /// When this transform is present, the decoder needs to add the green value to both red and blue.
+ /// There is no data associated with this transform.
+ ///
+ SubtractGreen = 2,
+
+ ///
+ /// If there are not many unique pixel values, it may be more efficient to create a color index array and replace the pixel values by the array's indices.
+ /// The color indexing transform achieves this.
+ ///
+ ColorIndexingTransform = 3,
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs b/src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs
new file mode 100644
index 0000000000..8ecaa2c83c
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8MacroBlock.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Contextual macroblock information.
+ ///
+ internal class Vp8MacroBlock
+ {
+ ///
+ /// Gets or sets non-zero AC/DC coeffs (4bit for luma + 4bit for chroma).
+ ///
+ public uint NoneZeroAcDcCoeffs { get; set; }
+
+ ///
+ /// Gets or sets non-zero DC coeff (1bit).
+ ///
+ public uint NoneZeroDcCoeffs { get; set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs b/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs
new file mode 100644
index 0000000000..cfac56d3c7
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8MacroBlockData.cs
@@ -0,0 +1,45 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Data needed to reconstruct a macroblock.
+ ///
+ internal class Vp8MacroBlockData
+ {
+ public Vp8MacroBlockData()
+ {
+ this.Modes = new byte[16];
+ this.Coeffs = new short[384];
+ }
+
+ ///
+ /// Gets or sets the coefficient. 384 coeffs = (16+4+4) * 4*4.
+ ///
+ public short[] Coeffs { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether its intra4x4.
+ ///
+ public bool IsI4x4 { get; set; }
+
+ ///
+ /// Gets the modes. One 16x16 mode (#0) or sixteen 4x4 modes.
+ ///
+ public byte[] Modes { get; }
+
+ ///
+ /// Gets or sets the chroma prediction mode.
+ ///
+ public byte UvMode { get; set; }
+
+ public uint NonZeroY { get; set; }
+
+ public uint NonZeroUv { get; set; }
+
+ public byte Skip { get; set; }
+
+ public byte Segment { get; set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8PictureHeader.cs b/src/ImageSharp/Formats/WebP/Vp8PictureHeader.cs
new file mode 100644
index 0000000000..3f80b13b29
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8PictureHeader.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ internal class Vp8PictureHeader
+ {
+ ///
+ /// Gets or sets the width of the image.
+ ///
+ public uint Width { get; set; }
+
+ ///
+ /// Gets or sets the Height of the image.
+ ///
+ public uint Height { get; set; }
+
+ ///
+ /// Gets or sets the horizontal scale.
+ ///
+ public sbyte XScale { get; set; }
+
+ ///
+ /// Gets or sets the vertical scale.
+ ///
+ public sbyte YScale { get; set; }
+
+ ///
+ /// Gets or sets the colorspace.
+ /// 0 - YUV color space similar to the YCrCb color space defined in.
+ /// 1 - Reserved for future use.
+ ///
+ public sbyte ColorSpace { get; set; }
+
+ ///
+ /// Gets or sets the clamp type.
+ /// 0 - Decoders are required to clamp the reconstructed pixel values to between 0 and 255 (inclusive).
+ /// 1 - Reconstructed pixel values are guaranteed to be between 0 and 255; no clamping is necessary.
+ ///
+ public sbyte ClampType { get; set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8Proba.cs b/src/ImageSharp/Formats/WebP/Vp8Proba.cs
new file mode 100644
index 0000000000..ae34f936d3
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8Proba.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Data for all frame-persistent probabilities.
+ ///
+ internal class Vp8Proba
+ {
+ private const int MbFeatureTreeProbs = 3;
+
+ public Vp8Proba()
+ {
+ this.Segments = new uint[MbFeatureTreeProbs];
+ this.Bands = new Vp8BandProbas[WebPConstants.NumTypes, WebPConstants.NumBands];
+ this.BandsPtr = new Vp8BandProbas[WebPConstants.NumTypes, 16 + 1];
+
+ for (int i = 0; i < WebPConstants.NumTypes; i++)
+ {
+ for (int j = 0; j < WebPConstants.NumBands; j++)
+ {
+ this.Bands[i, j] = new Vp8BandProbas();
+ }
+ }
+
+ for (int i = 0; i < WebPConstants.NumTypes; i++)
+ {
+ for (int j = 0; j < 17; j++)
+ {
+ this.BandsPtr[i, j] = new Vp8BandProbas();
+ }
+ }
+ }
+
+ public uint[] Segments { get; }
+
+ public Vp8BandProbas[,] Bands { get; }
+
+ public Vp8BandProbas[,] BandsPtr { get; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs b/src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs
new file mode 100644
index 0000000000..37ccc358b2
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8ProbaArray.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Probabilities associated to one of the contexts.
+ ///
+ internal class Vp8ProbaArray
+ {
+ public Vp8ProbaArray()
+ {
+ this.Probabilities = new byte[WebPConstants.NumProbas];
+ }
+
+ public byte[] Probabilities { get; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8Profile.cs b/src/ImageSharp/Formats/WebP/Vp8Profile.cs
new file mode 100644
index 0000000000..b1d757cb53
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8Profile.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// The version number setting enables or disables certain features in the bitstream.
+ ///
+ internal class Vp8Profile
+ {
+ ///
+ /// Gets or sets the reconstruction filter.
+ ///
+ public ReconstructionFilter ReconstructionFilter { get; set; }
+
+ ///
+ /// Gets or sets the loop filter.
+ ///
+ public LoopFilter LoopFilter { get; set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs b/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs
new file mode 100644
index 0000000000..ca9f4b69e9
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8QuantMatrix.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ internal class Vp8QuantMatrix
+ {
+ public int[] Y1Mat { get; } = new int[2];
+
+ public int[] Y2Mat { get; } = new int[2];
+
+ public int[] UvMat { get; } = new int[2];
+
+ ///
+ /// Gets or sets the U/V quantizer value.
+ ///
+ public int UvQuant { get; set; }
+
+ ///
+ /// Gets or sets the dithering amplitude (0 = off, max=255).
+ ///
+ public int Dither { get; set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs b/src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs
new file mode 100644
index 0000000000..27bce1f043
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8SegmentHeader.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Segment features.
+ ///
+ internal class Vp8SegmentHeader
+ {
+ private const int NumMbSegments = 4;
+
+ public Vp8SegmentHeader()
+ {
+ this.Quantizer = new byte[NumMbSegments];
+ this.FilterStrength = new byte[NumMbSegments];
+ }
+
+ public bool UseSegment { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether to update the segment map or not.
+ ///
+ public bool UpdateMap { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether to use delta values for quantizer and filter.
+ /// If this value is false, absolute values are used.
+ ///
+ public bool Delta { get; set; }
+
+ ///
+ /// Gets quantization changes.
+ ///
+ public byte[] Quantizer { get; private set; }
+
+ ///
+ /// Gets the filter strength for segments.
+ ///
+ public byte[] FilterStrength { get; private set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/Vp8TopSamples.cs b/src/ImageSharp/Formats/WebP/Vp8TopSamples.cs
new file mode 100644
index 0000000000..c6382b5c62
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/Vp8TopSamples.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ internal class Vp8TopSamples
+ {
+ public byte[] Y { get; } = new byte[16];
+
+ public byte[] U { get; } = new byte[8];
+
+ public byte[] V { get; } = new byte[8];
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs b/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs
new file mode 100644
index 0000000000..dfdc8281cf
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/WebPAlphaFilterType.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Enum for the different alpha filter types.
+ ///
+ internal enum WebPAlphaFilterType
+ {
+ ///
+ /// No filtering.
+ ///
+ None = 0,
+
+ ///
+ /// Horizontal filter.
+ ///
+ Horizontal = 1,
+
+ ///
+ /// Vertical filter.
+ ///
+ Vertical = 2,
+
+ ///
+ /// Gradient filter.
+ ///
+ Gradient = 3,
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs b/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs
new file mode 100644
index 0000000000..57bc16a66c
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/WebPBitsPerPixel.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Enumerates the available bits per pixel the webp image uses.
+ ///
+ public enum WebPBitsPerPixel : short
+ {
+ ///
+ /// 24 bits per pixel. Each pixel consists of 3 bytes.
+ ///
+ Pixel24 = 24,
+
+ ///
+ /// 32 bits per pixel. Each pixel consists of 4 bytes (an alpha channel is present).
+ ///
+ Pixel32 = 32
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/WebPChunkType.cs b/src/ImageSharp/Formats/WebP/WebPChunkType.cs
new file mode 100644
index 0000000000..a03f6bfb17
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/WebPChunkType.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Contains a list of different webp chunk types.
+ ///
+ public enum WebPChunkType : uint
+ {
+ ///
+ /// Header signaling the use of VP8 format.
+ ///
+ Vp8 = 0x56503820U,
+
+ ///
+ /// Header for a extended-VP8 chunk.
+ ///
+ Vp8L = 0x5650384CU,
+
+ ///
+ /// Header for a extended-VP8 chunk.
+ ///
+ Vp8X = 0x56503858U,
+
+ ///
+ /// Chunk contains information about the alpha channel.
+ ///
+ Alpha = 0x414C5048U,
+
+ ///
+ /// Chunk which contains a color profile.
+ ///
+ Iccp = 0x49434350U,
+
+ ///
+ /// Chunk which contains EXIF metadata about the image.
+ ///
+ Exif = 0x45584946U,
+
+ ///
+ /// Chunk contains XMP metadata about the image.
+ ///
+ Xmp = 0x584D5020U,
+
+ ///
+ /// For an animated image, this chunk contains the global parameters of the animation.
+ ///
+ AnimationParameter = 0x414E494D,
+
+ ///
+ /// For animated images, this chunk contains information about a single frame. If the Animation flag is not set, then this chunk SHOULD NOT be present.
+ ///
+ Animation = 0x414E4D46,
+
+ ///
+ /// TODO: not sure what this is for yet.
+ ///
+ FRGM = 0x4652474D,
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/WebPConstants.cs b/src/ImageSharp/Formats/WebP/WebPConstants.cs
new file mode 100644
index 0000000000..4908c133b0
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/WebPConstants.cs
@@ -0,0 +1,174 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Collections.Generic;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Constants used for decoding VP8 and VP8L bitstreams.
+ ///
+ internal static class WebPConstants
+ {
+ ///
+ /// The list of file extensions that equate to WebP.
+ ///
+ public static readonly IEnumerable FileExtensions = new[] { "webp" };
+
+ ///
+ /// The list of mimetypes that equate to a jpeg.
+ ///
+ public static readonly IEnumerable MimeTypes = new[] { "image/webp", };
+
+ ///
+ /// Signature which identifies a VP8 header.
+ ///
+ public static readonly byte[] Vp8MagicBytes =
+ {
+ 0x9D,
+ 0x01,
+ 0x2A
+ };
+
+ ///
+ /// The header bytes identifying RIFF file.
+ ///
+ public static readonly byte[] RiffFourCc =
+ {
+ 0x52, // R
+ 0x49, // I
+ 0x46, // F
+ 0x46 // F
+ };
+
+ ///
+ /// The header bytes identifying a WebP.
+ ///
+ public static readonly byte[] WebPHeader =
+ {
+ 0x57, // W
+ 0x45, // E
+ 0x42, // B
+ 0x50 // P
+ };
+
+ ///
+ /// Signature byte which identifies a VP8L header.
+ ///
+ public const byte Vp8LMagicByte = 0x2F;
+
+ ///
+ /// 3 bits reserved for version.
+ ///
+ public const int Vp8LVersionBits = 3;
+
+ ///
+ /// Bits for width and height infos of a VPL8 image.
+ ///
+ public const int Vp8LImageSizeBits = 14;
+
+ ///
+ /// Maximum number of color cache bits.
+ ///
+ public const int MaxColorCacheBits = 11;
+
+ ///
+ /// The maximum number of allowed transforms in a VP8L bitstream.
+ ///
+ public const int MaxNumberOfTransforms = 4;
+
+ public const int MaxAllowedCodeLength = 15;
+
+ public const int DefaultCodeLength = 8;
+
+ public const int HuffmanCodesPerMetaCode = 5;
+
+ public const uint ArgbBlack = 0xff000000;
+
+ public const int NumLiteralCodes = 256;
+
+ public const int NumLengthCodes = 24;
+
+ public const int NumDistanceCodes = 40;
+
+ public const int LengthTableBits = 7;
+
+ public const uint CodeLengthLiterals = 16;
+
+ public const int CodeLengthRepeatCode = 16;
+
+ public static readonly int[] CodeLengthExtraBits = { 2, 3, 7 };
+
+ public static readonly int[] CodeLengthRepeatOffsets = { 3, 3, 11 };
+
+ public static readonly int[] AlphabetSize =
+ {
+ NumLiteralCodes + NumLengthCodes,
+ NumLiteralCodes, NumLiteralCodes, NumLiteralCodes,
+ NumDistanceCodes
+ };
+
+ // VP8 constants from here on:
+ public const int NumMbSegments = 4;
+
+ public const int MaxNumPartitions = 8;
+
+ public const int NumTypes = 4;
+
+ public const int NumBands = 8;
+
+ public const int NumProbas = 11;
+
+ public const int NumCtx = 3;
+
+ // this is the common stride for enc/dec
+ public const int Bps = 32;
+
+ // intra prediction modes (TODO: maybe use an enum for this)
+ public const int DcPred = 0; // predict DC using row above and column to the left
+ public const int TmPred = 1; // propagate second differences a la "True Motion"
+ public const int VPred = 2; // predict rows using row above
+ public const int HPred = 3; // predict columns using column to the left
+
+ ///
+ /// How many extra lines are needed on the MB boundary for caching, given a filtering level.
+ /// Simple filter(1): up to 2 luma samples are read and 1 is written.
+ /// Complex filter(2): up to 4 luma samples are read and 3 are written. Same for U/V, so it's 8 samples total (because of the 2x upsampling).
+ ///
+ public static readonly byte[] FilterExtraRows = { 0, 2, 8 };
+
+ // Paragraph 9.9
+ public static readonly int[] Bands =
+ {
+ 0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 0
+ };
+
+ public static readonly short[] Scan =
+ {
+ 0 + (0 * Bps), 4 + (0 * Bps), 8 + (0 * Bps), 12 + (0 * Bps),
+ 0 + (4 * Bps), 4 + (4 * Bps), 8 + (4 * Bps), 12 + (4 * Bps),
+ 0 + (8 * Bps), 4 + (8 * Bps), 8 + (8 * Bps), 12 + (8 * Bps),
+ 0 + (12 * Bps), 4 + (12 * Bps), 8 + (12 * Bps), 12 + (12 * Bps)
+ };
+
+ // Residual decoding (Paragraph 13.2 / 13.3)
+ public static readonly byte[] Cat3 = { 173, 148, 140 };
+ public static readonly byte[] Cat4 = { 176, 155, 140, 135 };
+ public static readonly byte[] Cat5 = { 180, 157, 141, 134, 130 };
+ public static readonly byte[] Cat6 = { 254, 254, 243, 230, 196, 177, 153, 140, 133, 130, 129 };
+ public static readonly byte[] Zigzag = { 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 };
+
+ public static readonly sbyte[] YModesIntra4 =
+ {
+ -0, 1,
+ -1, 2,
+ -2, 3,
+ 4, 6,
+ -3, 5,
+ -4, -5,
+ -6, 7,
+ -7, 8,
+ -8, -9
+ };
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/WebPDecoder.cs b/src/ImageSharp/Formats/WebP/WebPDecoder.cs
new file mode 100644
index 0000000000..3273f300d4
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/WebPDecoder.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Image decoder for generating an image out of a webp stream.
+ ///
+ public sealed class WebPDecoder : IImageDecoder, IWebPDecoderOptions, IImageInfoDetector
+ {
+ ///
+ /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
+ ///
+ public bool IgnoreMetadata { get; set; }
+
+ ///
+ public Image Decode(Configuration configuration, Stream stream)
+ where TPixel : unmanaged, IPixel
+ {
+ Guard.NotNull(stream, nameof(stream));
+
+ return new WebPDecoderCore(configuration, this).Decode(stream);
+ }
+
+ ///
+ public IImageInfo Identify(Configuration configuration, Stream stream)
+ {
+ Guard.NotNull(stream, nameof(stream));
+
+ return new WebPDecoderCore(configuration, this).Identify(stream);
+ }
+
+ ///
+ public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream);
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs
new file mode 100644
index 0000000000..2971b26389
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/WebPDecoderCore.cs
@@ -0,0 +1,516 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers.Binary;
+using System.IO;
+
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.Metadata;
+using SixLabors.ImageSharp.Metadata.Profiles.Exif;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Performs the bitmap decoding operation.
+ ///
+ internal sealed class WebPDecoderCore
+ {
+ ///
+ /// Reusable buffer.
+ ///
+ private readonly byte[] buffer = new byte[4];
+
+ ///
+ /// The global configuration.
+ ///
+ private readonly Configuration configuration;
+
+ ///
+ /// Used for allocating memory during processing operations.
+ ///
+ private readonly MemoryAllocator memoryAllocator;
+
+ ///
+ /// The stream to decode from.
+ ///
+ private Stream currentStream;
+
+ ///
+ /// The webp specific metadata.
+ ///
+ private WebPMetadata webpMetadata;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration.
+ /// The options.
+ public WebPDecoderCore(Configuration configuration, IWebPDecoderOptions options)
+ {
+ this.configuration = configuration;
+ this.memoryAllocator = configuration.MemoryAllocator;
+ this.IgnoreMetadata = options.IgnoreMetadata;
+ }
+
+ ///
+ /// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
+ ///
+ public bool IgnoreMetadata { get; }
+
+ ///
+ /// Gets the decoded by this decoder instance.
+ ///
+ public ImageMetadata Metadata { get; private set; }
+
+ ///
+ /// Decodes the image from the specified and sets the data to the image.
+ ///
+ /// The pixel format.
+ /// The stream, where the image should be.
+ /// The decoded image.
+ public Image Decode(Stream stream)
+ where TPixel : unmanaged, IPixel
+ {
+ this.Metadata = new ImageMetadata();
+ this.currentStream = stream;
+
+ uint fileSize = this.ReadImageHeader();
+ WebPImageInfo imageInfo = this.ReadVp8Info();
+ if (imageInfo.Features != null && imageInfo.Features.Animation)
+ {
+ WebPThrowHelper.ThrowNotSupportedException("Animations are not supported");
+ }
+
+ var image = new Image(this.configuration, (int)imageInfo.Width, (int)imageInfo.Height, this.Metadata);
+ Buffer2D pixels = image.GetRootFramePixelBuffer();
+ if (imageInfo.IsLossLess)
+ {
+ var losslessDecoder = new WebPLosslessDecoder(imageInfo.Vp8LBitReader, this.memoryAllocator, this.configuration);
+ losslessDecoder.Decode(pixels, image.Width, image.Height);
+ }
+ else
+ {
+ var lossyDecoder = new WebPLossyDecoder(imageInfo.Vp8BitReader, this.memoryAllocator, this.configuration);
+ lossyDecoder.Decode(pixels, image.Width, image.Height, imageInfo);
+ }
+
+ // There can be optional chunks after the image data, like EXIF and XMP.
+ if (imageInfo.Features != null)
+ {
+ this.ParseOptionalChunks(imageInfo.Features);
+ }
+
+ return image;
+ }
+
+ ///
+ /// Reads the raw image information from the specified stream.
+ ///
+ /// The containing image data.
+ public IImageInfo Identify(Stream stream)
+ {
+ this.currentStream = stream;
+
+ this.ReadImageHeader();
+ WebPImageInfo imageInfo = this.ReadVp8Info();
+
+ return new ImageInfo(new PixelTypeInfo((int)imageInfo.BitsPerPixel), (int)imageInfo.Width, (int)imageInfo.Height, this.Metadata);
+ }
+
+ ///
+ /// Reads and skips over the image header.
+ ///
+ /// The chunk size in bytes.
+ private uint ReadImageHeader()
+ {
+ // Skip FourCC header, we already know its a RIFF file at this point.
+ this.currentStream.Skip(4);
+
+ // Read file size.
+ // The size of the file in bytes starting at offset 8.
+ // The file size in the header is the total size of the chunks that follow plus 4 bytes for the ‘WEBP’ FourCC.
+ uint chunkSize = this.ReadChunkSize();
+
+ // Skip 'WEBP' from the header.
+ this.currentStream.Skip(4);
+
+ return chunkSize;
+ }
+
+ private WebPImageInfo ReadVp8Info()
+ {
+ this.Metadata = new ImageMetadata();
+ this.webpMetadata = this.Metadata.GetFormatMetadata(WebPFormat.Instance);
+
+ WebPChunkType chunkType = this.ReadChunkType();
+
+ switch (chunkType)
+ {
+ case WebPChunkType.Vp8:
+ return this.ReadVp8Header();
+ case WebPChunkType.Vp8L:
+ return this.ReadVp8LHeader();
+ case WebPChunkType.Vp8X:
+ return this.ReadVp8XHeader();
+ }
+
+ WebPThrowHelper.ThrowImageFormatException("Unrecognized VP8 header");
+
+ return new WebPImageInfo();
+ }
+
+ ///
+ /// Reads an the extended webp file header. An extended file header consists of:
+ /// - A 'VP8X' chunk with information about features used in the file.
+ /// - An optional 'ICCP' chunk with color profile.
+ /// - An optional 'ANIM' chunk with animation control data.
+ /// - An optional 'ALPH' chunk with alpha channel data.
+ /// After the image header, image data will follow. After that optional image metadata chunks (EXIF and XMP) can follow.
+ ///
+ /// Information about this webp image.
+ private WebPImageInfo ReadVp8XHeader()
+ {
+ uint chunkSize = this.ReadChunkSize();
+
+ // The first byte contains information about the image features used.
+ // The first two bit of it are reserved and should be 0. TODO: should an exception be thrown if its not the case, or just ignore it?
+ byte imageFeatures = (byte)this.currentStream.ReadByte();
+
+ // If bit 3 is set, a ICC Profile Chunk should be present.
+ bool isIccPresent = (imageFeatures & (1 << 5)) != 0;
+
+ // If bit 4 is set, any of the frames of the image contain transparency information ("alpha" chunk).
+ bool isAlphaPresent = (imageFeatures & (1 << 4)) != 0;
+
+ // If bit 5 is set, a EXIF metadata should be present.
+ bool isExifPresent = (imageFeatures & (1 << 3)) != 0;
+
+ // If bit 6 is set, XMP metadata should be present.
+ bool isXmpPresent = (imageFeatures & (1 << 2)) != 0;
+
+ // If bit 7 is set, animation should be present.
+ bool isAnimationPresent = (imageFeatures & (1 << 1)) != 0;
+
+ // 3 reserved bytes should follow which are supposed to be zero.
+ this.currentStream.Read(this.buffer, 0, 3);
+
+ // 3 bytes for the width.
+ this.currentStream.Read(this.buffer, 0, 3);
+ this.buffer[3] = 0;
+ uint width = (uint)BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1;
+
+ // 3 bytes for the height.
+ this.currentStream.Read(this.buffer, 0, 3);
+ this.buffer[3] = 0;
+ uint height = (uint)BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1;
+
+ // Optional chunks ICCP, ALPH and ANIM can follow here.
+ WebPChunkType chunkType;
+ if (isIccPresent)
+ {
+ chunkType = this.ReadChunkType();
+ if (chunkType is WebPChunkType.Iccp)
+ {
+ uint iccpChunkSize = this.ReadChunkSize();
+ if (!this.IgnoreMetadata)
+ {
+ var iccpData = new byte[iccpChunkSize];
+ this.currentStream.Read(iccpData, 0, (int)iccpChunkSize);
+ var profile = new IccProfile(iccpData);
+ if (profile.CheckIsValid())
+ {
+ this.Metadata.IccProfile = profile;
+ }
+ }
+ else
+ {
+ this.currentStream.Skip((int)iccpChunkSize);
+ }
+ }
+ }
+
+ if (isAnimationPresent)
+ {
+ this.webpMetadata.Animated = true;
+
+ return new WebPImageInfo()
+ {
+ Width = width,
+ Height = height,
+ Features = new WebPFeatures()
+ {
+ Animation = true
+ }
+ };
+ }
+
+ byte[] alphaData = null;
+ byte alphaChunkHeader = 0;
+ if (isAlphaPresent)
+ {
+ chunkType = this.ReadChunkType();
+ if (chunkType != WebPChunkType.Alpha)
+ {
+ WebPThrowHelper.ThrowImageFormatException($"unexpected chunk type {chunkType}, expected ALPH chunk is missing");
+ }
+
+ uint alphaChunkSize = this.ReadChunkSize();
+ alphaChunkHeader = (byte)this.currentStream.ReadByte();
+ alphaData = new byte[alphaChunkSize - 1];
+ this.currentStream.Read(alphaData, 0, alphaData.Length);
+ }
+
+ var features = new WebPFeatures()
+ {
+ Animation = isAnimationPresent,
+ Alpha = isAlphaPresent,
+ AlphaData = alphaData,
+ AlphaChunkHeader = alphaChunkHeader,
+ ExifProfile = isExifPresent,
+ IccProfile = isIccPresent,
+ XmpMetaData = isXmpPresent
+ };
+
+ // A VP8 or VP8L chunk should follow here.
+ chunkType = this.ReadChunkType();
+
+ // TOOD: check if VP8 or VP8L info about the dimensions match VP8X info
+ switch (chunkType)
+ {
+ case WebPChunkType.Vp8:
+ return this.ReadVp8Header(features);
+ case WebPChunkType.Vp8L:
+ return this.ReadVp8LHeader(features);
+ }
+
+ WebPThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header");
+
+ return new WebPImageInfo();
+ }
+
+ ///
+ /// Reads the header of a lossy webp image.
+ ///
+ /// Webp features.
+ /// Information about this webp image.
+ private WebPImageInfo ReadVp8Header(WebPFeatures features = null)
+ {
+ this.webpMetadata.Format = WebPFormatType.Lossy;
+
+ // VP8 data size (not including this 4 bytes).
+ this.currentStream.Read(this.buffer, 0, 4);
+ uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
+
+ // remaining counts the available image data payload.
+ uint remaining = dataSize;
+
+ // Paragraph 9.1 https://tools.ietf.org/html/rfc6386#page-30
+ // Frame tag that contains four fields:
+ // - A 1-bit frame type (0 for key frames, 1 for interframes).
+ // - A 3-bit version number.
+ // - A 1-bit show_frame flag.
+ // - A 19-bit field containing the size of the first data partition in bytes.
+ this.currentStream.Read(this.buffer, 0, 3);
+ uint frameTag = (uint)(this.buffer[0] | (this.buffer[1] << 8) | (this.buffer[2] << 16));
+ remaining -= 3;
+ bool isKeyFrame = (frameTag & 0x1) is 0;
+ if (!isKeyFrame)
+ {
+ WebPThrowHelper.ThrowImageFormatException("VP8 header indicates the image is not a key frame");
+ }
+
+ uint version = (frameTag >> 1) & 0x7;
+ if (version > 3)
+ {
+ WebPThrowHelper.ThrowImageFormatException($"VP8 header indicates unknown profile {version}");
+ }
+
+ bool showFrame = ((frameTag >> 4) & 0x1) is 1;
+ if (!showFrame)
+ {
+ WebPThrowHelper.ThrowImageFormatException("VP8 header indicates that the first frame is invisible");
+ }
+
+ uint partitionLength = frameTag >> 5;
+ if (partitionLength > dataSize)
+ {
+ WebPThrowHelper.ThrowImageFormatException("VP8 header contains inconsistent size information");
+ }
+
+ // Check for VP8 magic bytes.
+ this.currentStream.Read(this.buffer, 0, 3);
+ if (!this.buffer.AsSpan().Slice(0, 3).SequenceEqual(WebPConstants.Vp8MagicBytes))
+ {
+ WebPThrowHelper.ThrowImageFormatException("VP8 magic bytes not found");
+ }
+
+ this.currentStream.Read(this.buffer, 0, 4);
+ uint tmp = (uint)BinaryPrimitives.ReadInt16LittleEndian(this.buffer);
+ uint width = tmp & 0x3fff;
+ sbyte xScale = (sbyte)(tmp >> 6);
+ tmp = (uint)BinaryPrimitives.ReadInt16LittleEndian(this.buffer.AsSpan(2));
+ uint height = tmp & 0x3fff;
+ sbyte yScale = (sbyte)(tmp >> 6);
+ remaining -= 7;
+ if (width is 0 || height is 0)
+ {
+ WebPThrowHelper.ThrowImageFormatException("width or height can not be zero");
+ }
+
+ if (partitionLength > remaining)
+ {
+ WebPThrowHelper.ThrowImageFormatException("bad partition length");
+ }
+
+ var vp8FrameHeader = new Vp8FrameHeader()
+ {
+ KeyFrame = true,
+ Profile = (sbyte)version,
+ PartitionLength = partitionLength
+ };
+
+ var bitReader = new Vp8BitReader(
+ this.currentStream,
+ remaining,
+ this.memoryAllocator,
+ partitionLength);
+ bitReader.Remaining = remaining;
+
+ return new WebPImageInfo()
+ {
+ Width = width,
+ Height = height,
+ XScale = xScale,
+ YScale = yScale,
+ BitsPerPixel = features?.Alpha is true ? WebPBitsPerPixel.Pixel32 : WebPBitsPerPixel.Pixel24,
+ IsLossLess = false,
+ Features = features,
+ Vp8Profile = (sbyte)version,
+ Vp8FrameHeader = vp8FrameHeader,
+ Vp8BitReader = bitReader
+ };
+ }
+
+ ///
+ /// Reads the header of a lossless webp image.
+ ///
+ /// Webp image features.
+ /// Information about this image.
+ private WebPImageInfo ReadVp8LHeader(WebPFeatures features = null)
+ {
+ this.webpMetadata.Format = WebPFormatType.Lossless;
+
+ // VP8 data size.
+ uint imageDataSize = this.ReadChunkSize();
+
+ var bitReader = new Vp8LBitReader(this.currentStream, imageDataSize, this.memoryAllocator);
+
+ // One byte signature, should be 0x2f.
+ uint signature = bitReader.ReadValue(8);
+ if (signature != WebPConstants.Vp8LMagicByte)
+ {
+ WebPThrowHelper.ThrowImageFormatException("Invalid VP8L signature");
+ }
+
+ // The first 28 bits of the bitstream specify the width and height of the image.
+ uint width = bitReader.ReadValue(WebPConstants.Vp8LImageSizeBits) + 1;
+ uint height = bitReader.ReadValue(WebPConstants.Vp8LImageSizeBits) + 1;
+ if (width is 0 || height is 0)
+ {
+ WebPThrowHelper.ThrowImageFormatException("width or height can not be zero");
+ }
+
+ // The alphaIsUsed flag should be set to 0 when all alpha values are 255 in the picture, and 1 otherwise.
+ // TODO: this flag value is not used yet
+ bool alphaIsUsed = bitReader.ReadBit();
+
+ // The next 3 bits are the version. The version number is a 3 bit code that must be set to 0.
+ // Any other value should be treated as an error.
+ uint version = bitReader.ReadValue(WebPConstants.Vp8LVersionBits);
+ if (version != 0)
+ {
+ WebPThrowHelper.ThrowNotSupportedException($"Unexpected version number {version} found in VP8L header");
+ }
+
+ return new WebPImageInfo()
+ {
+ Width = width,
+ Height = height,
+ BitsPerPixel = WebPBitsPerPixel.Pixel32,
+ IsLossLess = true,
+ Features = features,
+ Vp8LBitReader = bitReader
+ };
+ }
+
+ ///
+ /// Parses optional metadata chunks. There SHOULD be at most one chunk of each type ('EXIF' and 'XMP ').
+ /// If there are more such chunks, readers MAY ignore all except the first one.
+ /// Also, a file may possibly contain both 'EXIF' and 'XMP ' chunks.
+ ///
+ /// The webp features.
+ private void ParseOptionalChunks(WebPFeatures features)
+ {
+ if (this.IgnoreMetadata || (features.ExifProfile is false && features.XmpMetaData is false))
+ {
+ return;
+ }
+
+ while (this.currentStream.Position < this.currentStream.Length)
+ {
+ // Read chunk header.
+ WebPChunkType chunkType = this.ReadChunkType();
+ uint chunkLength = this.ReadChunkSize();
+
+ if (chunkType is WebPChunkType.Exif)
+ {
+ var exifData = new byte[chunkLength];
+ this.currentStream.Read(exifData, 0, (int)chunkLength);
+ this.Metadata.ExifProfile = new ExifProfile(exifData);
+ }
+ else
+ {
+ // Skip XMP chunk data for now.
+ this.currentStream.Skip((int)chunkLength);
+ }
+ }
+ }
+
+ ///
+ /// Identifies the chunk type from the chunk.
+ ///
+ ///
+ /// Thrown if the input stream is not valid.
+ ///
+ private WebPChunkType ReadChunkType()
+ {
+ if (this.currentStream.Read(this.buffer, 0, 4) is 4)
+ {
+ var chunkType = (WebPChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
+ this.webpMetadata.ChunkTypes.Enqueue(chunkType);
+ return chunkType;
+ }
+
+ throw new ImageFormatException("Invalid WebP data.");
+ }
+
+ ///
+ /// Reads the chunk size. If Chunk Size is odd, a single padding byte will be added to the payload,
+ /// so the chunk size will be increased by 1 in those cases.
+ ///
+ /// The chunk size in bytes.
+ private uint ReadChunkSize()
+ {
+ if (this.currentStream.Read(this.buffer, 0, 4) is 4)
+ {
+ uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
+ return (chunkSize % 2 is 0) ? chunkSize : chunkSize + 1;
+ }
+
+ throw new ImageFormatException("Invalid WebP data.");
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/WebPFeatures.cs b/src/ImageSharp/Formats/WebP/WebPFeatures.cs
new file mode 100644
index 0000000000..3fd0350766
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/WebPFeatures.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Image features of a VP8X image.
+ ///
+ internal class WebPFeatures
+ {
+ ///
+ /// Gets or sets a value indicating whether this image has a ICC Profile.
+ ///
+ public bool IccProfile { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this image has a alpha channel.
+ ///
+ public bool Alpha { get; set; }
+
+ ///
+ /// Gets or sets the alpha data, if an ALPH chunk is present.
+ ///
+ public byte[] AlphaData { get; set; }
+
+ ///
+ /// Gets or sets the alpha chunk header.
+ ///
+ public byte AlphaChunkHeader { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this image has a EXIF Profile.
+ ///
+ public bool ExifProfile { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this image has XMP Metadata.
+ ///
+ public bool XmpMetaData { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this image is a animation.
+ ///
+ public bool Animation { get; set; }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/WebPFormat.cs b/src/ImageSharp/Formats/WebP/WebPFormat.cs
new file mode 100644
index 0000000000..48a5952587
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/WebPFormat.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Collections.Generic;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Registers the image encoders, decoders and mime type detectors for the WebP format
+ ///
+ public sealed class WebPFormat : IImageFormat
+ {
+ ///
+ /// Gets the current instance.
+ ///
+ public static WebPFormat Instance { get; } = new WebPFormat();
+
+ ///
+ public string Name => "WebP";
+
+ ///
+ public string DefaultMimeType => "image/webp";
+
+ ///
+ public IEnumerable MimeTypes => WebPConstants.MimeTypes;
+
+ ///
+ public IEnumerable FileExtensions => WebPConstants.FileExtensions;
+
+ ///
+ public WebPMetadata CreateDefaultFormatMetadata() => new WebPMetadata();
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/WebPFormatType.cs b/src/ImageSharp/Formats/WebP/WebPFormatType.cs
new file mode 100644
index 0000000000..291281d006
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/WebPFormatType.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Info about the webp format used.
+ ///
+ public enum WebPFormatType
+ {
+ ///
+ /// Unknown webp format.
+ ///
+ Unknown,
+
+ ///
+ /// The lossless webp format.
+ ///
+ Lossless,
+
+ ///
+ /// The lossy webp format.
+ ///
+ Lossy,
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs b/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs
new file mode 100644
index 0000000000..dc0ecadc8a
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/WebPImageFormatDetector.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Detects WebP file headers.
+ ///
+ public sealed class WebPImageFormatDetector : IImageFormatDetector
+ {
+ ///
+ public int HeaderSize => 12;
+
+ ///
+ public IImageFormat DetectFormat(ReadOnlySpan header)
+ {
+ return this.IsSupportedFileFormat(header) ? WebPFormat.Instance : null;
+ }
+
+ private bool IsSupportedFileFormat(ReadOnlySpan header)
+ {
+ return header.Length >= this.HeaderSize &&
+ this.IsRiffContainer(header) &&
+ this.IsWebPFile(header);
+ }
+
+ ///
+ /// Checks, if the header starts with a valid RIFF FourCC.
+ ///
+ /// The header bytes.
+ /// True, if its a valid RIFF FourCC.
+ private bool IsRiffContainer(ReadOnlySpan header)
+ {
+ return header.Slice(0, 4).SequenceEqual(WebPConstants.RiffFourCc);
+ }
+
+ ///
+ /// Checks if 'WEBP' is present in the header.
+ ///
+ /// The header bytes.
+ /// True, if its a webp file.
+ private bool IsWebPFile(ReadOnlySpan header)
+ {
+ return header.Slice(8, 4).SequenceEqual(WebPConstants.WebPHeader);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/WebPImageInfo.cs b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs
new file mode 100644
index 0000000000..19a72c0c28
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/WebPImageInfo.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ internal class WebPImageInfo
+ {
+ ///
+ /// Gets or sets the bitmap width in pixels (signed integer).
+ ///
+ public uint Width { get; set; }
+
+ ///
+ /// Gets or sets the bitmap height in pixels (signed integer).
+ ///
+ public uint Height { get; set; }
+
+ public sbyte XScale { get; set; }
+
+ public sbyte YScale { get; set; }
+
+ ///
+ /// Gets or sets the bits per pixel.
+ ///
+ public WebPBitsPerPixel BitsPerPixel { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this image uses lossless compression.
+ ///
+ public bool IsLossLess { get; set; }
+
+ ///
+ /// Gets or sets additional features present in a VP8X image.
+ ///
+ public WebPFeatures Features { get; set; }
+
+ ///
+ /// Gets or sets the VP8 profile / version. Valid values are between 0 and 3. Default value will be the invalid value -1.
+ ///
+ public int Vp8Profile { get; set; } = -1;
+
+ ///
+ /// Gets or sets the VP8 frame header.
+ ///
+ public Vp8FrameHeader Vp8FrameHeader { get; set; }
+
+ ///
+ /// Gets or sets the VP8L bitreader. Will be null, if its not lossless image.
+ ///
+ public Vp8LBitReader Vp8LBitReader { get; set; } = null;
+
+ ///
+ /// Gets or sets the VP8 bitreader. Will be null, if its not a lossy image.
+ ///
+ public Vp8BitReader Vp8BitReader { get; set; } = null;
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/WebPLookupTables.cs b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs
new file mode 100644
index 0000000000..26c4be7736
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/WebPLookupTables.cs
@@ -0,0 +1,580 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Collections.Generic;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ internal static class WebPLookupTables
+ {
+ public static readonly Dictionary Abs0;
+
+ public static readonly Dictionary Clip1;
+
+ public static readonly Dictionary Sclip1;
+
+ public static readonly Dictionary Sclip2;
+
+ public static readonly byte[,][] ModesProba = new byte[10, 10][];
+
+ public static readonly int[] CodeToPlane =
+ {
+ 0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a,
+ 0x26, 0x2a, 0x38, 0x05, 0x37, 0x39, 0x15, 0x1b, 0x36, 0x3a,
+ 0x25, 0x2b, 0x48, 0x04, 0x47, 0x49, 0x14, 0x1c, 0x35, 0x3b,
+ 0x46, 0x4a, 0x24, 0x2c, 0x58, 0x45, 0x4b, 0x34, 0x3c, 0x03,
+ 0x57, 0x59, 0x13, 0x1d, 0x56, 0x5a, 0x23, 0x2d, 0x44, 0x4c,
+ 0x55, 0x5b, 0x33, 0x3d, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1e,
+ 0x66, 0x6a, 0x22, 0x2e, 0x54, 0x5c, 0x43, 0x4d, 0x65, 0x6b,
+ 0x32, 0x3e, 0x78, 0x01, 0x77, 0x79, 0x53, 0x5d, 0x11, 0x1f,
+ 0x64, 0x6c, 0x42, 0x4e, 0x76, 0x7a, 0x21, 0x2f, 0x75, 0x7b,
+ 0x31, 0x3f, 0x63, 0x6d, 0x52, 0x5e, 0x00, 0x74, 0x7c, 0x41,
+ 0x4f, 0x10, 0x20, 0x62, 0x6e, 0x30, 0x73, 0x7d, 0x51, 0x5f,
+ 0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70
+ };
+
+ // 31 ^ clz(i)
+ public static readonly byte[] LogTable8bit =
+ {
+ 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
+ };
+
+ // Paragraph 14.1
+ public static readonly int[] DcTable =
+ {
+ 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
+ };
+
+ // Paragraph 14.1
+ public static readonly int[] AcTable =
+ {
+ 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
+ };
+
+ // Paragraph 13
+ public static readonly byte[,,,] CoeffsUpdateProba =
+ {
+ {
+ {
+ { 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 }
+ }
+ }
+ };
+
+ // Paragraph 13.5: Default Token Probability Table.
+ public static readonly byte[,,,] DefaultCoeffsProba =
+ {
+ {
+ {
+ { 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 }
+ }
+ }
+ };
+
+ static WebPLookupTables()
+ {
+ // TODO: maybe use hashset here
+ Abs0 = new Dictionary();
+ for (int i = -255; i <= 255; ++i)
+ {
+ Abs0[i] = (byte)((i < 0) ? -i : i);
+ }
+
+ Clip1 = new Dictionary();
+ for (int i = -255; i <= 255 + 255; ++i)
+ {
+ Clip1[i] = (byte)((i < 0) ? 0 : (i > 255) ? 255 : i);
+ }
+
+ Sclip1 = new Dictionary();
+ for (int i = -1020; i <= 1020; ++i)
+ {
+ Sclip1[i] = (sbyte)((i < -128) ? -128 : (i > 127) ? 127 : i);
+ }
+
+ Sclip2 = new Dictionary();
+ for (int i = -112; i <= 112; ++i)
+ {
+ Sclip2[i] = (sbyte)((i < -16) ? -16 : (i > 15) ? 15 : i);
+ }
+
+ InitializeModesProbabilities();
+ }
+
+ private static void InitializeModesProbabilities()
+ {
+ // Paragraph 11.5
+ ModesProba[0, 0] = new byte[] { 231, 120, 48, 89, 115, 113, 120, 152, 112 };
+ ModesProba[0, 1] = new byte[] { 152, 179, 64, 126, 170, 118, 46, 70, 95 };
+ ModesProba[0, 2] = new byte[] { 175, 69, 143, 80, 85, 82, 72, 155, 103 };
+ ModesProba[0, 3] = new byte[] { 56, 58, 10, 171, 218, 189, 17, 13, 152 };
+ ModesProba[0, 4] = new byte[] { 114, 26, 17, 163, 44, 195, 21, 10, 173 };
+ ModesProba[0, 5] = new byte[] { 121, 24, 80, 195, 26, 62, 44, 64, 85 };
+ ModesProba[0, 6] = new byte[] { 144, 71, 10, 38, 171, 213, 144, 34, 26 };
+ ModesProba[0, 7] = new byte[] { 170, 46, 55, 19, 136, 160, 33, 206, 71 };
+ ModesProba[0, 8] = new byte[] { 63, 20, 8, 114, 114, 208, 12, 9, 226 };
+ ModesProba[0, 9] = new byte[] { 81, 40, 11, 96, 182, 84, 29, 16, 36 };
+ ModesProba[1, 0] = new byte[] { 134, 183, 89, 137, 98, 101, 106, 165, 148 };
+ ModesProba[1, 1] = new byte[] { 72, 187, 100, 130, 157, 111, 32, 75, 80 };
+ ModesProba[1, 2] = new byte[] { 66, 102, 167, 99, 74, 62, 40, 234, 128 };
+ ModesProba[1, 3] = new byte[] { 41, 53, 9, 178, 241, 141, 26, 8, 107 };
+ ModesProba[1, 4] = new byte[] { 74, 43, 26, 146, 73, 166, 49, 23, 157 };
+ ModesProba[1, 5] = new byte[] { 65, 38, 105, 160, 51, 52, 31, 115, 128 };
+ ModesProba[1, 6] = new byte[] { 104, 79, 12, 27, 217, 255, 87, 17, 7 };
+ ModesProba[1, 7] = new byte[] { 87, 68, 71, 44, 114, 51, 15, 186, 23 };
+ ModesProba[1, 8] = new byte[] { 47, 41, 14, 110, 182, 183, 21, 17, 194 };
+ ModesProba[1, 9] = new byte[] { 66, 45, 25, 102, 197, 189, 23, 18, 22 };
+ ModesProba[2, 0] = new byte[] { 88, 88, 147, 150, 42, 46, 45, 196, 205 };
+ ModesProba[2, 1] = new byte[] { 43, 97, 183, 117, 85, 38, 35, 179, 61 };
+ ModesProba[2, 2] = new byte[] { 39, 53, 200, 87, 26, 21, 43, 232, 171 };
+ ModesProba[2, 3] = new byte[] { 56, 34, 51, 104, 114, 102, 29, 93, 77 };
+ ModesProba[2, 4] = new byte[] { 39, 28, 85, 171, 58, 165, 90, 98, 64 };
+ ModesProba[2, 5] = new byte[] { 34, 22, 116, 206, 23, 34, 43, 166, 73 };
+ ModesProba[2, 6] = new byte[] { 107, 54, 32, 26, 51, 1, 81, 43, 31 };
+ ModesProba[2, 7] = new byte[] { 68, 25, 106, 22, 64, 171, 36, 225, 114 };
+ ModesProba[2, 8] = new byte[] { 34, 19, 21, 102, 132, 188, 16, 76, 124 };
+ ModesProba[2, 9] = new byte[] { 62, 18, 78, 95, 85, 57, 50, 48, 51 };
+ ModesProba[3, 0] = new byte[] { 193, 101, 35, 159, 215, 111, 89, 46, 111 };
+ ModesProba[3, 1] = new byte[] { 60, 148, 31, 172, 219, 228, 21, 18, 111 };
+ ModesProba[3, 2] = new byte[] { 112, 113, 77, 85, 179, 255, 38, 120, 114 };
+ ModesProba[3, 3] = new byte[] { 40, 42, 1, 196, 245, 209, 10, 25, 109 };
+ ModesProba[3, 4] = new byte[] { 88, 43, 29, 140, 166, 213, 37, 43, 154 };
+ ModesProba[3, 5] = new byte[] { 61, 63, 30, 155, 67, 45, 68, 1, 209 };
+ ModesProba[3, 6] = new byte[] { 100, 80, 8, 43, 154, 1, 51, 26, 71 };
+ ModesProba[3, 7] = new byte[] { 142, 78, 78, 16, 255, 128, 34, 197, 171 };
+ ModesProba[3, 8] = new byte[] { 41, 40, 5, 102, 211, 183, 4, 1, 221 };
+ ModesProba[3, 9] = new byte[] { 51, 50, 17, 168, 209, 192, 23, 25, 82 };
+ ModesProba[4, 0] = new byte[] { 138, 31, 36, 171, 27, 166, 38, 44, 229 };
+ ModesProba[4, 1] = new byte[] { 67, 87, 58, 169, 82, 115, 26, 59, 179 };
+ ModesProba[4, 2] = new byte[] { 63, 59, 90, 180, 59, 166, 93, 73, 154 };
+ ModesProba[4, 3] = new byte[] { 40, 40, 21, 116, 143, 209, 34, 39, 175 };
+ ModesProba[4, 4] = new byte[] { 47, 15, 16, 183, 34, 223, 49, 45, 183 };
+ ModesProba[4, 5] = new byte[] { 46, 17, 33, 183, 6, 98, 15, 32, 183 };
+ ModesProba[4, 6] = new byte[] { 57, 46, 22, 24, 128, 1, 54, 17, 37 };
+ ModesProba[4, 7] = new byte[] { 65, 32, 73, 115, 28, 128, 23, 128, 205 };
+ ModesProba[4, 8] = new byte[] { 40, 3, 9, 115, 51, 192, 18, 6, 223 };
+ ModesProba[4, 9] = new byte[] { 87, 37, 9, 115, 59, 77, 64, 21, 47 };
+ ModesProba[5, 0] = new byte[] { 104, 55, 44, 218, 9, 54, 53, 130, 226 };
+ ModesProba[5, 1] = new byte[] { 64, 90, 70, 205, 40, 41, 23, 26, 57 };
+ ModesProba[5, 2] = new byte[] { 54, 57, 112, 184, 5, 41, 38, 166, 213 };
+ ModesProba[5, 3] = new byte[] { 30, 34, 26, 133, 152, 116, 10, 32, 134 };
+ ModesProba[5, 4] = new byte[] { 39, 19, 53, 221, 26, 114, 32, 73, 255 };
+ ModesProba[5, 5] = new byte[] { 31, 9, 65, 234, 2, 15, 1, 118, 73 };
+ ModesProba[5, 6] = new byte[] { 75, 32, 12, 51, 192, 255, 160, 43, 51 };
+ ModesProba[5, 7] = new byte[] { 88, 31, 35, 67, 102, 85, 55, 186, 85 };
+ ModesProba[5, 8] = new byte[] { 56, 21, 23, 111, 59, 205, 45, 37, 192 };
+ ModesProba[5, 9] = new byte[] { 55, 38, 70, 124, 73, 102, 1, 34, 98 };
+ ModesProba[6, 0] = new byte[] { 125, 98, 42, 88, 104, 85, 117, 175, 82 };
+ ModesProba[6, 1] = new byte[] { 95, 84, 53, 89, 128, 100, 113, 101, 45 };
+ ModesProba[6, 2] = new byte[] { 75, 79, 123, 47, 51, 128, 81, 171, 1 };
+ ModesProba[6, 3] = new byte[] { 57, 17, 5, 71, 102, 57, 53, 41, 49 };
+ ModesProba[6, 4] = new byte[] { 38, 33, 13, 121, 57, 73, 26, 1, 85 };
+ ModesProba[6, 5] = new byte[] { 41, 10, 67, 138, 77, 110, 90, 47, 114 };
+ ModesProba[6, 6] = new byte[] { 115, 21, 2, 10, 102, 255, 166, 23, 6 };
+ ModesProba[6, 7] = new byte[] { 101, 29, 16, 10, 85, 128, 101, 196, 26 };
+ ModesProba[6, 8] = new byte[] { 57, 18, 10, 102, 102, 213, 34, 20, 43 };
+ ModesProba[6, 9] = new byte[] { 117, 20, 15, 36, 163, 128, 68, 1, 26 };
+ ModesProba[7, 0] = new byte[] { 102, 61, 71, 37, 34, 53, 31, 243, 192 };
+ ModesProba[7, 1] = new byte[] { 69, 60, 71, 38, 73, 119, 28, 222, 37 };
+ ModesProba[7, 2] = new byte[] { 68, 45, 128, 34, 1, 47, 11, 245, 171 };
+ ModesProba[7, 3] = new byte[] { 62, 17, 19, 70, 146, 85, 55, 62, 70 };
+ ModesProba[7, 4] = new byte[] { 37, 43, 37, 154, 100, 163, 85, 160, 1 };
+ ModesProba[7, 5] = new byte[] { 63, 9, 92, 136, 28, 64, 32, 201, 85 };
+ ModesProba[7, 6] = new byte[] { 75, 15, 9, 9, 64, 255, 184, 119, 16 };
+ ModesProba[7, 7] = new byte[] { 86, 6, 28, 5, 64, 255, 25, 248, 1 };
+ ModesProba[7, 8] = new byte[] { 56, 8, 17, 132, 137, 255, 55, 116, 128 };
+ ModesProba[7, 9] = new byte[] { 58, 15, 20, 82, 135, 57, 26, 121, 40 };
+ ModesProba[8, 0] = new byte[] { 164, 50, 31, 137, 154, 133, 25, 35, 218 };
+ ModesProba[8, 1] = new byte[] { 51, 103, 44, 131, 131, 123, 31, 6, 158 };
+ ModesProba[8, 2] = new byte[] { 86, 40, 64, 135, 148, 224, 45, 183, 128 };
+ ModesProba[8, 3] = new byte[] { 22, 26, 17, 131, 240, 154, 14, 1, 209 };
+ ModesProba[8, 4] = new byte[] { 45, 16, 21, 91, 64, 222, 7, 1, 197 };
+ ModesProba[8, 5] = new byte[] { 56, 21, 39, 155, 60, 138, 23, 102, 213 };
+ ModesProba[8, 6] = new byte[] { 83, 12, 13, 54, 192, 255, 68, 47, 28 };
+ ModesProba[8, 7] = new byte[] { 85, 26, 85, 85, 128, 128, 32, 146, 171 };
+ ModesProba[8, 8] = new byte[] { 18, 11, 7, 63, 144, 171, 4, 4, 246 };
+ ModesProba[8, 9] = new byte[] { 35, 27, 10, 146, 174, 171, 12, 26, 128 };
+ ModesProba[9, 0] = new byte[] { 190, 80, 35, 99, 180, 80, 126, 54, 45 };
+ ModesProba[9, 1] = new byte[] { 85, 126, 47, 87, 176, 51, 41, 20, 32 };
+ ModesProba[9, 2] = new byte[] { 101, 75, 128, 139, 118, 146, 116, 128, 85 };
+ ModesProba[9, 3] = new byte[] { 56, 41, 15, 176, 236, 85, 37, 9, 62 };
+ ModesProba[9, 4] = new byte[] { 71, 30, 17, 119, 118, 255, 17, 18, 138 };
+ ModesProba[9, 5] = new byte[] { 101, 38, 60, 138, 55, 70, 43, 26, 142 };
+ ModesProba[9, 6] = new byte[] { 146, 36, 19, 30, 171, 255, 97, 27, 20 };
+ ModesProba[9, 7] = new byte[] { 138, 45, 61, 62, 219, 1, 81, 188, 64 };
+ ModesProba[9, 8] = new byte[] { 32, 41, 20, 117, 151, 142, 20, 21, 163 };
+ ModesProba[9, 9] = new byte[] { 112, 19, 12, 61, 195, 128, 48, 4, 24 };
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs
new file mode 100644
index 0000000000..1f0d363295
--- /dev/null
+++ b/src/ImageSharp/Formats/WebP/WebPLosslessDecoder.cs
@@ -0,0 +1,1027 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.WebP
+{
+ ///
+ /// Decoder for lossless webp images. This code is a port of libwebp, which can be found here: https://chromium.googlesource.com/webm/libwebp
+ ///
+ ///
+ /// The lossless specification can be found here:
+ /// https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification
+ ///
+ internal sealed class WebPLosslessDecoder
+ {
+ ///
+ /// A bit reader for reading lossless webp streams.
+ ///
+ private readonly Vp8LBitReader bitReader;
+
+ ///
+ /// The global configuration.
+ ///
+ private readonly Configuration configuration;
+
+ ///
+ /// Used for allocating memory during processing operations.
+ ///
+ private readonly MemoryAllocator memoryAllocator;
+
+ private static readonly int BitsSpecialMarker = 0x100;
+
+ private static readonly int NumArgbCacheRows = 16;
+
+ private static readonly uint PackedNonLiteralCode = 0;
+
+ private static readonly int CodeToPlaneCodes = WebPLookupTables.CodeToPlane.Length;
+
+ private static readonly int FixedTableSize = (630 * 3) + 410;
+
+ private static readonly int[] TableSize =
+ {
+ FixedTableSize + 654,
+ FixedTableSize + 656,
+ FixedTableSize + 658,
+ FixedTableSize + 662,
+ FixedTableSize + 670,
+ FixedTableSize + 686,
+ FixedTableSize + 718,
+ FixedTableSize + 782,
+ FixedTableSize + 912,
+ FixedTableSize + 1168,
+ FixedTableSize + 1680,
+ FixedTableSize + 2704
+ };
+
+ private static readonly byte[] CodeLengthCodeOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
+
+ private static readonly int NumCodeLengthCodes = CodeLengthCodeOrder.Length;
+
+ private static readonly byte[] LiteralMap =
+ {
+ 0, 1, 1, 1, 0
+ };
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Bitreader to read from the stream.
+ /// Used for allocating memory during processing operations.
+ /// The configuration.
+ public WebPLosslessDecoder(Vp8LBitReader bitReader, MemoryAllocator memoryAllocator, Configuration configuration)
+ {
+ this.bitReader = bitReader;
+ this.memoryAllocator = memoryAllocator;
+ this.configuration = configuration;
+ }
+
+ ///
+ /// Decodes the image from the stream using the bitreader.
+ ///
+ /// The pixel format.
+ /// The pixel buffer to store the decoded data.
+ /// The width of the image.
+ /// The height of the image.
+ public void Decode(Buffer2D pixels, int width, int height)
+ where TPixel : unmanaged, IPixel
+ {
+ using (var decoder = new Vp8LDecoder(width, height, this.memoryAllocator))
+ {
+ this.DecodeImageStream(decoder, width, height, true);
+ this.DecodeImageData(decoder, decoder.Pixels.Memory.Span);
+ this.DecodePixelValues(decoder, pixels);
+ }
+ }
+
+ public IMemoryOwner DecodeImageStream(Vp8LDecoder decoder, int xSize, int ySize, bool isLevel0)
+ {
+ int transformXSize = xSize;
+ int transformYSize = ySize;
+ int numberOfTransformsPresent = 0;
+ if (isLevel0)
+ {
+ decoder.Transforms = new List(WebPConstants.MaxNumberOfTransforms);
+
+ // Next bit indicates, if a transformation is present.
+ while (this.bitReader.ReadBit())
+ {
+ if (numberOfTransformsPresent > WebPConstants.MaxNumberOfTransforms)
+ {
+ WebPThrowHelper.ThrowImageFormatException($"The maximum number of transforms of {WebPConstants.MaxNumberOfTransforms} was exceeded");
+ }
+
+ this.ReadTransformation(transformXSize, transformYSize, decoder);
+ if (decoder.Transforms[numberOfTransformsPresent].TransformType == Vp8LTransformType.ColorIndexingTransform)
+ {
+ transformXSize = LosslessUtils.SubSampleSize(transformXSize, decoder.Transforms[numberOfTransformsPresent].Bits);
+ }
+
+ numberOfTransformsPresent++;
+ }
+ }
+ else
+ {
+ decoder.Metadata = new Vp8LMetadata();
+ }
+
+ // Color cache.
+ bool colorCachePresent = this.bitReader.ReadBit();
+ int colorCacheBits = 0;
+ int colorCacheSize = 0;
+ if (colorCachePresent)
+ {
+ colorCacheBits = (int)this.bitReader.ReadValue(4);
+ bool coloCacheBitsIsValid = colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits;
+ if (!coloCacheBitsIsValid)
+ {
+ WebPThrowHelper.ThrowImageFormatException("Invalid color cache bits found");
+ }
+ }
+
+ // Read the Huffman codes (may recurse).
+ this.ReadHuffmanCodes(decoder, transformXSize, transformYSize, colorCacheBits, isLevel0);
+ decoder.Metadata.ColorCacheSize = colorCacheSize;
+
+ // Finish setting up the color-cache.
+ if (colorCachePresent)
+ {
+ decoder.Metadata.ColorCache = new ColorCache();
+ colorCacheSize = 1 << colorCacheBits;
+ decoder.Metadata.ColorCacheSize = colorCacheSize;
+ if (!(colorCacheBits >= 1 && colorCacheBits <= WebPConstants.MaxColorCacheBits))
+ {
+ WebPThrowHelper.ThrowImageFormatException("Invalid color cache bits found");
+ }
+
+ decoder.Metadata.ColorCache.Init(colorCacheBits);
+ }
+ else
+ {
+ decoder.Metadata.ColorCacheSize = 0;
+ }
+
+ this.UpdateDecoder(decoder, transformXSize, transformYSize);
+ if (isLevel0)
+ {
+ // level 0 complete.
+ return null;
+ }
+
+ // Use the Huffman trees to decode the LZ77 encoded data.
+ IMemoryOwner pixelData = this.memoryAllocator.Allocate(decoder.Width * decoder.Height, AllocationOptions.Clean);
+ this.DecodeImageData(decoder, pixelData.GetSpan());
+
+ return pixelData;
+ }
+
+ private void DecodePixelValues(Vp8LDecoder decoder, Buffer2D pixels)
+ where TPixel : unmanaged, IPixel
+ {
+ Span pixelData = decoder.Pixels.GetSpan();
+ int width = decoder.Width;
+
+ // Apply reverse transformations, if any are present.
+ this.ApplyInverseTransforms(decoder, pixelData);
+
+ Span pixelDataAsBytes = MemoryMarshal.Cast(pixelData);
+ for (int y = 0; y < decoder.Height; y++)
+ {
+ Span row = pixelDataAsBytes.Slice(y * width * 4, width * 4);
+ Span pixelSpan = pixels.GetRowSpan(y);
+ PixelOperations.Instance.FromBgra32Bytes(
+ this.configuration,
+ row,
+ pixelSpan,
+ width);
+ }
+ }
+
+ private void DecodeImageData(Vp8LDecoder decoder, Span pixelData)
+ {
+ int lastPixel = 0;
+ int width = decoder.Width;
+ int height = decoder.Height;
+ int row = lastPixel / width;
+ int col = lastPixel % width;
+ int lenCodeLimit = WebPConstants.NumLiteralCodes + WebPConstants.NumLengthCodes;
+ int colorCacheSize = decoder.Metadata.ColorCacheSize;
+ ColorCache colorCache = decoder.Metadata.ColorCache;
+ int colorCacheLimit = lenCodeLimit + colorCacheSize;
+ int mask = decoder.Metadata.HuffmanMask;
+ HTreeGroup[] hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row);
+
+ int totalPixels = width * height;
+ int decodedPixels = 0;
+ int lastCached = decodedPixels;
+ while (decodedPixels < totalPixels)
+ {
+ int code;
+ if ((col & mask) is 0)
+ {
+ hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row);
+ }
+
+ if (hTreeGroup[0].IsTrivialCode)
+ {
+ pixelData[decodedPixels] = hTreeGroup[0].LiteralArb;
+ this.AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached);
+ continue;
+ }
+
+ this.bitReader.FillBitWindow();
+ if (hTreeGroup[0].UsePackedTable)
+ {
+ code = (int)this.ReadPackedSymbols(hTreeGroup, pixelData, decodedPixels);
+ if (this.bitReader.IsEndOfStream())
+ {
+ break;
+ }
+
+ if (code == PackedNonLiteralCode)
+ {
+ this.AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached);
+ continue;
+ }
+ }
+ else
+ {
+ code = (int)this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Green]);
+ }
+
+ if (this.bitReader.IsEndOfStream())
+ {
+ break;
+ }
+
+ // Literal
+ if (code < WebPConstants.NumLiteralCodes)
+ {
+ if (hTreeGroup[0].IsTrivialLiteral)
+ {
+ pixelData[decodedPixels] = hTreeGroup[0].LiteralArb | ((uint)code << 8);
+ }
+ else
+ {
+ uint red = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Red]);
+ this.bitReader.FillBitWindow();
+ uint blue = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Blue]);
+ uint alpha = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Alpha]);
+ if (this.bitReader.IsEndOfStream())
+ {
+ break;
+ }
+
+ int pixelIdx = decodedPixels;
+ pixelData[pixelIdx] = (uint)(((byte)alpha << 24) | ((byte)red << 16) | ((byte)code << 8) | (byte)blue);
+ }
+
+ this.AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached);
+ }
+ else if (code < lenCodeLimit)
+ {
+ // Backward reference is used.
+ int lengthSym = code - WebPConstants.NumLiteralCodes;
+ int length = this.GetCopyLength(lengthSym);
+ uint distSymbol = this.ReadSymbol(hTreeGroup[0].HTrees[HuffIndex.Dist]);
+ this.bitReader.FillBitWindow();
+ int distCode = this.GetCopyDistance((int)distSymbol);
+ int dist = this.PlaneCodeToDistance(width, distCode);
+ if (this.bitReader.IsEndOfStream())
+ {
+ break;
+ }
+
+ this.CopyBlock(pixelData, decodedPixels, dist, length);
+ decodedPixels += length;
+ col += length;
+ while (col >= width)
+ {
+ col -= width;
+ ++row;
+ }
+
+ if ((col & mask) != 0)
+ {
+ hTreeGroup = this.GetHTreeGroupForPos(decoder.Metadata, col, row);
+ }
+
+ if (colorCache != null)
+ {
+ while (lastCached < decodedPixels)
+ {
+ colorCache.Insert(pixelData[lastCached]);
+ lastCached++;
+ }
+ }
+ }
+ else if (code < colorCacheLimit)
+ {
+ // Color cache should be used.
+ int key = code - lenCodeLimit;
+ while (lastCached < decodedPixels)
+ {
+ colorCache.Insert(pixelData[lastCached]);
+ lastCached++;
+ }
+
+ pixelData[decodedPixels] = colorCache.Lookup(key);
+ this.AdvanceByOne(ref col, ref row, width, colorCache, ref decodedPixels, pixelData, ref lastCached);
+ }
+ else
+ {
+ WebPThrowHelper.ThrowImageFormatException("Webp parsing error");
+ }
+ }
+ }
+
+ private void AdvanceByOne(ref int col, ref int row, int width, ColorCache colorCache, ref int decodedPixels, Span pixelData, ref int lastCached)
+ {
+ ++col;
+ decodedPixels++;
+ if (col >= width)
+ {
+ col = 0;
+ ++row;
+
+ if (colorCache != null)
+ {
+ while (lastCached < decodedPixels)
+ {
+ colorCache.Insert(pixelData[lastCached]);
+ lastCached++;
+ }
+ }
+ }
+ }
+
+ private void ReadHuffmanCodes(Vp8LDecoder decoder, int xSize, int ySize, int colorCacheBits, bool allowRecursion)
+ {
+ int maxAlphabetSize = 0;
+ int numHTreeGroups = 1;
+ int numHTreeGroupsMax = 1;
+
+ // If the next bit is zero, there is only one meta Huffman code used everywhere in the image. No more data is stored.
+ // If this bit is one, the image uses multiple meta Huffman codes. These meta Huffman codes are stored as an entropy image.
+ if (allowRecursion && this.bitReader.ReadBit())
+ {
+ // Use meta Huffman codes.
+ uint huffmanPrecision = this.bitReader.ReadValue(3) + 2;
+ int huffmanXSize = LosslessUtils.SubSampleSize(xSize, (int)huffmanPrecision);
+ int huffmanYSize = LosslessUtils.SubSampleSize(ySize, (int)huffmanPrecision);
+ int huffmanPixels = huffmanXSize * huffmanYSize;
+ IMemoryOwner huffmanImage = this.DecodeImageStream(decoder, huffmanXSize, huffmanYSize, false);
+ Span huffmanImageSpan = huffmanImage.GetSpan();
+ decoder.Metadata.HuffmanSubSampleBits = (int)huffmanPrecision;
+ for (int i = 0; i < huffmanPixels; ++i)
+ {
+ // The huffman data is stored in red and green bytes.
+ uint group = (huffmanImageSpan[i] >> 8) & 0xffff;
+ huffmanImageSpan[i] = group;
+ if (group >= numHTreeGroupsMax)
+ {
+ numHTreeGroupsMax = (int)group + 1;
+ }
+ }
+
+ numHTreeGroups = numHTreeGroupsMax;
+ decoder.Metadata.HuffmanImage = huffmanImage;
+ }
+
+ // Find maximum alphabet size for the hTree group.
+ for (int j = 0; j < WebPConstants.HuffmanCodesPerMetaCode; j++)
+ {
+ int alphabetSize = WebPConstants.AlphabetSize[j];
+ if (j == 0 && colorCacheBits > 0)
+ {
+ alphabetSize += 1 << colorCacheBits;
+ }
+
+ if (maxAlphabetSize < alphabetSize)
+ {
+ maxAlphabetSize = alphabetSize;
+ }
+ }
+
+ int tableSize = TableSize[colorCacheBits];
+ var huffmanTables = new HuffmanCode[numHTreeGroups * tableSize];
+ var hTreeGroups = new HTreeGroup[numHTreeGroups];
+ Span huffmanTable = huffmanTables.AsSpan();
+ for (int i = 0; i < numHTreeGroupsMax; i++)
+ {
+ hTreeGroups[i] = new HTreeGroup(HuffmanUtils.HuffmanPackedTableSize);
+ HTreeGroup hTreeGroup = hTreeGroups[i];
+ int totalSize = 0;
+ bool isTrivialLiteral = true;
+ int maxBits = 0;
+ var codeLengths = new int[maxAlphabetSize];
+ for (int j = 0; j < WebPConstants.HuffmanCodesPerMetaCode; j++)
+ {
+ int alphabetSize = WebPConstants.AlphabetSize[j];
+ if (j == 0 && colorCacheBits > 0)
+ {
+ alphabetSize += 1 << colorCacheBits;
+ }
+
+ int size = this.ReadHuffmanCode(alphabetSize, codeLengths, huffmanTable);
+ if (size is 0)
+ {
+ WebPThrowHelper.ThrowImageFormatException("Huffman table size is zero");
+ }
+
+ hTreeGroup.HTrees.Add(huffmanTable.ToArray());
+
+ if (isTrivialLiteral && LiteralMap[j] == 1)
+ {
+ isTrivialLiteral = huffmanTable[0].BitsUsed == 0;
+ }
+
+ totalSize += huffmanTable[0].BitsUsed;
+ huffmanTable = huffmanTable.Slice(size);
+
+ if (j <= HuffIndex.Alpha)
+ {
+ int localMaxBits = codeLengths[0];
+ int k;
+ for (k = 1; k < alphabetSize; ++k)
+ {
+ if (codeLengths[k] > localMaxBits)
+ {
+ localMaxBits = codeLengths[k];
+ }
+ }
+
+ maxBits += localMaxBits;
+ }
+ }
+
+ hTreeGroup.IsTrivialLiteral = isTrivialLiteral;
+ hTreeGroup.IsTrivialCode = false;
+ if (isTrivialLiteral)
+ {
+ uint red = hTreeGroup.HTrees[HuffIndex.Red].First().Value;
+ uint blue = hTreeGroup.HTrees[HuffIndex.Blue].First().Value;
+ uint green = hTreeGroup.HTrees[HuffIndex.Green].First().Value;
+ uint alpha = hTreeGroup.HTrees[HuffIndex.Alpha].First().Value;
+ hTreeGroup.LiteralArb = (alpha << 24) | (red << 16) | blue;
+ if (totalSize == 0 && green < WebPConstants.NumLiteralCodes)
+ {
+ hTreeGroup.IsTrivialCode = true;
+ hTreeGroup.LiteralArb |= green << 8;
+ }
+ }
+
+ hTreeGroup.UsePackedTable = !hTreeGroup.IsTrivialCode && maxBits < HuffmanUtils.HuffmanPackedBits;
+ if (hTreeGroup.UsePackedTable)
+ {
+ this.BuildPackedTable(hTreeGroup);
+ }
+ }
+
+ decoder.Metadata.NumHTreeGroups = numHTreeGroups;
+ decoder.Metadata.HTreeGroups = hTreeGroups;
+ decoder.Metadata.HuffmanTables = huffmanTables;
+ }
+
+ private int ReadHuffmanCode(int alphabetSize, int[] codeLengths, Span table)
+ {
+ bool simpleCode = this.bitReader.ReadBit();
+ for (int i = 0; i < alphabetSize; i++)
+ {
+ codeLengths[i] = 0;
+ }
+
+ if (simpleCode)
+ {
+ // (i) Simple Code Length Code.
+ // This variant is used in the special case when only 1 or 2 Huffman code lengths are non - zero,
+ // and are in the range of[0, 255].All other Huffman code lengths are implicitly zeros.
+
+ // Read symbols, codes & code lengths directly.
+ uint numSymbols = this.bitReader.ReadValue(1) + 1;
+ uint firstSymbolLenCode = this.bitReader.ReadValue(1);
+
+ // The first code is either 1 bit or 8 bit code.
+ uint symbol = this.bitReader.ReadValue((firstSymbolLenCode is 0) ? 1 : 8);
+ codeLengths[symbol] = 1;
+
+ // The second code (if present), is always 8 bit long.
+ if (numSymbols is 2)
+ {
+ symbol = this.bitReader.ReadValue(8);
+ codeLengths[symbol] = 1;
+ }
+ }
+ else
+ {
+ // (ii) Normal Code Length Code:
+ // The code lengths of a Huffman code are read as follows: num_code_lengths specifies the number of code lengths;
+ // the rest of the code lengths (according to the order in kCodeLengthCodeOrder) are zeros.
+ var codeLengthCodeLengths = new int[NumCodeLengthCodes];
+ uint numCodes = this.bitReader.ReadValue(4) + 4;
+ if (numCodes > NumCodeLengthCodes)
+ {
+ WebPThrowHelper.ThrowImageFormatException("Bitstream error, numCodes has an invalid value");
+ }
+
+ for (int i = 0; i < numCodes; i++)
+ {
+ codeLengthCodeLengths[CodeLengthCodeOrder[i]] = (int)this.bitReader.ReadValue(3);
+ }
+
+ this.ReadHuffmanCodeLengths(table.ToArray(), codeLengthCodeLengths, alphabetSize, codeLengths);
+ }
+
+ int size = HuffmanUtils.BuildHuffmanTable(table, HuffmanUtils.HuffmanTableBits, codeLengths, alphabetSize);
+
+ return size;
+ }
+
+ private void ReadHuffmanCodeLengths(HuffmanCode[] table, int[] codeLengthCodeLengths, int numSymbols, int[] codeLengths)
+ {
+ int maxSymbol;
+ int symbol = 0;
+ int prevCodeLen = WebPConstants.DefaultCodeLength;
+ int size = HuffmanUtils.BuildHuffmanTable(table, WebPConstants.LengthTableBits, codeLengthCodeLengths, NumCodeLengthCodes);
+ if (size is 0)
+ {
+ WebPThrowHelper.ThrowImageFormatException("Error building huffman table");
+ }
+
+ if (this.bitReader.ReadBit())
+ {
+ int lengthNBits = 2 + (2 * (int)this.bitReader.ReadValue(3));
+ maxSymbol = 2 + (int)this.bitReader.ReadValue(lengthNBits);
+ }
+ else
+ {
+ maxSymbol = numSymbols;
+ }
+
+ while (symbol < numSymbols)
+ {
+ if (maxSymbol-- is 0)
+ {
+ break;
+ }
+
+ this.bitReader.FillBitWindow();
+ ulong prefetchBits = this.bitReader.PrefetchBits();
+ ulong idx = prefetchBits & 127;
+ HuffmanCode huffmanCode = table[idx];
+ this.bitReader.AdvanceBitPosition(huffmanCode.BitsUsed);
+ uint codeLen = huffmanCode.Value;
+ if (codeLen < WebPConstants.CodeLengthLiterals)
+ {
+ codeLengths[symbol++] = (int)codeLen;
+ if (codeLen != 0)
+ {
+ prevCodeLen = (int)codeLen;
+ }
+ }
+ else
+ {
+ bool usePrev = codeLen == WebPConstants.CodeLengthRepeatCode;
+ uint slot = codeLen - WebPConstants.CodeLengthLiterals;
+ int extraBits = WebPConstants.CodeLengthExtraBits[slot];
+ int repeatOffset = WebPConstants.CodeLengthRepeatOffsets[slot];
+ int repeat = (int)(this.bitReader.ReadValue(extraBits) + repeatOffset);
+ if (symbol + repeat > numSymbols)
+ {
+ // TODO: not sure, if this should be treated as an error here
+ return;
+ }
+
+ int length = usePrev ? prevCodeLen : 0;
+ while (repeat-- > 0)
+ {
+ codeLengths[symbol++] = length;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Reads the transformations, if any are present.
+ ///
+ /// The width of the image.
+ /// The height of the image.
+ /// Vp8LDecoder where the transformations will be stored.
+ private void ReadTransformation(int xSize, int ySize, Vp8LDecoder decoder)
+ {
+ var transformType = (Vp8LTransformType)this.bitReader.ReadValue(2);
+ var transform = new Vp8LTransform(transformType, xSize, ySize);
+
+ // Each transform is allowed to be used only once.
+ if (decoder.Transforms.Any(t => t.TransformType == transform.TransformType))
+ {
+ WebPThrowHelper.ThrowImageFormatException("Each transform can only be present once");
+ }
+
+ switch (transformType)
+ {
+ case Vp8LTransformType.SubtractGreen:
+ // There is no data associated with this transform.
+ break;
+ case Vp8LTransformType.ColorIndexingTransform:
+ // The transform data contains color table size and the entries in the color table.
+ // 8 bit value for color table size.
+ uint numColors = this.bitReader.ReadValue(8) + 1;
+ int bits = (numColors > 16) ? 0
+ : (numColors > 4) ? 1
+ : (numColors > 2) ? 2
+ : 3;
+ transform.Bits = bits;
+ using (IMemoryOwner colorMap = this.DecodeImageStream(decoder, (int)numColors, 1, false))
+ {
+ int finalNumColors = 1 << (8 >> transform.Bits);
+ IMemoryOwner newColorMap = this.memoryAllocator.Allocate(finalNumColors, AllocationOptions.Clean);
+ LosslessUtils.ExpandColorMap((int)numColors, colorMap.GetSpan(), newColorMap.GetSpan());
+ transform.Data = newColorMap;
+ }
+
+ break;
+
+ case Vp8LTransformType.PredictorTransform:
+ case Vp8LTransformType.CrossColorTransform:
+ {
+ // The first 3 bits of prediction data define the block width and height in number of bits.
+ transform.Bits = (int)this.bitReader.ReadValue(3) + 2;
+ int blockWidth = LosslessUtils.SubSampleSize(transform.XSize, transform.Bits);
+ int blockHeight = LosslessUtils.SubSampleSize(transform.YSize, transform.Bits);
+ IMemoryOwner transformData = this.DecodeImageStream(decoder, blockWidth, blockHeight, false);
+ transform.Data = transformData;
+ break;
+ }
+ }
+
+ decoder.Transforms.Add(transform);
+ }
+
+ ///
+ /// A WebP lossless image can go through four different types of transformation before being entropy encoded.
+ /// This will reverses the transformations, if any are present.
+ ///
+ /// The decoder holding the transformation infos.
+ /// The pixel data to apply the transformation.
+ private void ApplyInverseTransforms(Vp8LDecoder decoder, Span pixelData)
+ {
+ List transforms = decoder.Transforms;
+ for (int i = transforms.Count - 1; i >= 0; i--)
+ {
+ Vp8LTransformType transformType = transforms[i].TransformType;
+ switch (transformType)
+ {
+ case Vp8LTransformType.PredictorTransform:
+ using (IMemoryOwner