├── tests ├── Assets │ ├── JPEGsnoop.zip │ ├── baseline │ │ ├── lake.jpg │ │ ├── cramps.jpg │ │ ├── HETissueSlide.jpg │ │ ├── cramps.jpg.high.png │ │ ├── lake.jpg.high.png │ │ ├── lake.jpg.low-diff.png │ │ └── cramps.jpg.low-diff.png │ ├── huffman_progressive │ │ ├── progress.jpg │ │ ├── progress.jpg.high.png │ │ ├── progress.jpg.low-diff.png │ │ ├── yellowcat_progressive_restart.jpg │ │ ├── yellowcat_progressive_restart.jpg.high.png │ │ └── yellowcat_progressive_restart.jpg.low-diff.png │ ├── huffman_sequential │ │ ├── testorig12.jpg │ │ ├── testorig12.jpg.high.png │ │ └── testorig12.jpg.low-diff.png │ ├── huffman_lossless │ │ ├── lossless1_s22.jpg │ │ ├── lossless2_s22.jpg │ │ ├── lossless3_s22.jpg │ │ ├── lossless4_s22.jpg │ │ ├── lossless5_s22.jpg │ │ ├── lossless6_s22.jpg │ │ ├── lossless7_s22.jpg │ │ ├── lossless1_s22.jpg.high.png │ │ ├── lossless2_s22.jpg.high.png │ │ ├── lossless3_s22.jpg.high.png │ │ ├── lossless4_s22.jpg.high.png │ │ ├── lossless5_s22.jpg.high.png │ │ ├── lossless6_s22.jpg.high.png │ │ ├── lossless7_s22.jpg.high.png │ │ ├── lossless1_s22.jpg.low-diff.png │ │ ├── lossless2_s22.jpg.low-diff.png │ │ ├── lossless3_s22.jpg.low-diff.png │ │ ├── lossless4_s22.jpg.low-diff.png │ │ ├── lossless5_s22.jpg.low-diff.png │ │ ├── lossless6_s22.jpg.low-diff.png │ │ ├── lossless7_s22.jpg.low-diff.png │ │ ├── lossless1_s22.jpg.txt │ │ ├── lossless2_s22.jpg.txt │ │ ├── lossless3_s22.jpg.txt │ │ ├── lossless4_s22.jpg.txt │ │ ├── lossless5_s22.jpg.txt │ │ ├── lossless6_s22.jpg.txt │ │ └── lossless7_s22.jpg.txt │ ├── arithmetic_sequential │ │ ├── zackthecat_arith.jpg │ │ ├── yellowcat_arith_restart.jpg │ │ ├── zackthecat_arith_restart.jpg │ │ ├── zackthecat_arith.jpg.high.png │ │ ├── zackthecat_arith.jpg.low-diff.png │ │ ├── yellowcat_arith_restart.jpg.high.png │ │ ├── zackthecat_arith_restart.jpg.high.png │ │ ├── yellowcat_arith_restart.jpg.low-diff.png │ │ ├── zackthecat_arith_restart.jpg.low-diff.png │ │ ├── zackthecat_arith.jpg.txt │ │ ├── yellowcat_arith_restart.jpg.txt │ │ └── zackthecat_arith_restart.jpg.txt │ └── arithmetic_progressive │ │ ├── yellowcat_progressive_arith.jpg │ │ ├── yellowcat_progressive_arith.jpg.high.png │ │ ├── yellowcat_progressive_arith_restart.jpg │ │ ├── yellowcat_progressive_arith.jpg.low-diff.png │ │ ├── yellowcat_progressive_arith_restart.jpg.high.png │ │ └── yellowcat_progressive_arith_restart.jpg.low-diff.png ├── JpegLibrary.Benchmarks │ ├── Program.cs │ ├── SkipLocalsInitAttribute.cs │ ├── JpegLibrary.Benchmarks.csproj │ ├── NullWriteStream.cs │ ├── JpegBufferInputReader.cs │ ├── NullBufferWriter.cs │ ├── JpegBufferOutputWriter.cs │ ├── DecoderBenchmark.cs │ ├── JpegRgbaInputReader.cs │ ├── ColorConverters │ │ └── JpegRgbToYCbCrConverter.cs │ └── JpegRgbToYCbCrComponentConverter.cs ├── Directory.Build.targets ├── Directory.Build.props └── JpegLibrary.Tests │ ├── JpegLibrary.Tests.csproj │ ├── Decoder │ ├── HuffmanProgressiveDecodeTests.cs │ ├── ArithmeticProgressiveDecodeTests.cs │ ├── HuffmanSequentialDecodeTests.cs │ ├── ArithmeticSequentialDecodeTests.cs │ ├── HuffmanLosslessDecodeTests.cs │ └── MetadataIdentifyTests.cs │ ├── Optimizer │ └── OptimizerTests.cs │ └── Utils │ ├── ImageHelper.cs │ └── JpegExtendingOutputWriter.cs ├── THIRD-PARTY-NOTICES.md ├── src ├── JpegLibrary │ ├── JpegMarkerHelper.cs │ ├── JpegElementPrecision.cs │ ├── Utils │ │ ├── SkipLocalsInitAttribute.cs │ │ └── NullableAnnotations.cs │ ├── JpegHuffmanDecodingComponent.cs │ ├── JpegTranscodeComponent.cs │ ├── JpegBlockOutputWriter.cs │ ├── JpegArithmeticDecodingComponent.cs │ ├── JpegArithmeticStatistics.cs │ ├── JpegHuffmanEncodingComponent.cs │ ├── JpegBlockInputReader.cs │ ├── JpegLibrary.csproj │ ├── JpegHuffmanCanonicalCode.cs │ ├── JpegHuffmanEncodingTableBuilderCollection.cs │ ├── JpegBlock8x8.cs │ ├── ScanDecoder │ │ ├── JpegScanDecoder.cs │ │ └── JpegHuffmanScanDecoder.cs │ ├── JpegMathHelper.cs │ ├── JpegHuffmanEncodingTable.cs │ ├── JpegStandardQuantizationTable.cs │ ├── JpegZigZag.cs │ └── JpegArithmeticDecodingTable.cs └── Directory.Build.props ├── version.json ├── apps ├── JpegOptimize │ ├── JpegOptimize.csproj │ ├── Program.cs │ ├── OptimizeAction.cs │ └── MemoryPoolBufferWriter.cs ├── JpegDecode │ ├── JpegDecode.csproj │ ├── Program.cs │ ├── JpegBufferOutputWriter8Bit.cs │ ├── JpegBufferOutputWriterGreaterThan8Bit.cs │ ├── DecodeAction.cs │ ├── JpegBufferOutputWriterLessThan8Bit.cs │ └── MemoryPoolBufferWriter.cs ├── JpegEncode │ ├── JpegEncode.csproj │ ├── JpegBufferInputReader.cs │ ├── Program.cs │ ├── EncodeAction.cs │ └── JpegRgbToYCbCrConverter.cs └── JpegDebugDump │ ├── JpegDebugDump.csproj │ ├── Program.cs │ ├── DebugDumpAction.cs │ └── JpegExtendingOutputWriter.cs ├── LICENSE ├── Directory.Build.props ├── README.md └── azure-pipelines.yml /tests/Assets/JPEGsnoop.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/JPEGsnoop.zip -------------------------------------------------------------------------------- /tests/Assets/baseline/lake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/baseline/lake.jpg -------------------------------------------------------------------------------- /tests/Assets/baseline/cramps.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/baseline/cramps.jpg -------------------------------------------------------------------------------- /tests/Assets/baseline/HETissueSlide.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/baseline/HETissueSlide.jpg -------------------------------------------------------------------------------- /tests/Assets/baseline/cramps.jpg.high.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/baseline/cramps.jpg.high.png -------------------------------------------------------------------------------- /tests/Assets/baseline/lake.jpg.high.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/baseline/lake.jpg.high.png -------------------------------------------------------------------------------- /tests/Assets/baseline/lake.jpg.low-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/baseline/lake.jpg.low-diff.png -------------------------------------------------------------------------------- /tests/Assets/baseline/cramps.jpg.low-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/baseline/cramps.jpg.low-diff.png -------------------------------------------------------------------------------- /tests/Assets/huffman_progressive/progress.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_progressive/progress.jpg -------------------------------------------------------------------------------- /tests/Assets/huffman_sequential/testorig12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_sequential/testorig12.jpg -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless1_s22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless1_s22.jpg -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless2_s22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless2_s22.jpg -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless3_s22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless3_s22.jpg -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless4_s22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless4_s22.jpg -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless5_s22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless5_s22.jpg -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless6_s22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless6_s22.jpg -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless7_s22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless7_s22.jpg -------------------------------------------------------------------------------- /tests/Assets/arithmetic_sequential/zackthecat_arith.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/arithmetic_sequential/zackthecat_arith.jpg -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless1_s22.jpg.high.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless1_s22.jpg.high.png -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless2_s22.jpg.high.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless2_s22.jpg.high.png -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless3_s22.jpg.high.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless3_s22.jpg.high.png -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless4_s22.jpg.high.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless4_s22.jpg.high.png -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless5_s22.jpg.high.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless5_s22.jpg.high.png -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless6_s22.jpg.high.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless6_s22.jpg.high.png -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless7_s22.jpg.high.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless7_s22.jpg.high.png -------------------------------------------------------------------------------- /tests/Assets/huffman_progressive/progress.jpg.high.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_progressive/progress.jpg.high.png -------------------------------------------------------------------------------- /tests/Assets/huffman_sequential/testorig12.jpg.high.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_sequential/testorig12.jpg.high.png -------------------------------------------------------------------------------- /tests/Assets/huffman_progressive/progress.jpg.low-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_progressive/progress.jpg.low-diff.png -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless1_s22.jpg.low-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless1_s22.jpg.low-diff.png -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless2_s22.jpg.low-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless2_s22.jpg.low-diff.png -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless3_s22.jpg.low-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless3_s22.jpg.low-diff.png -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless4_s22.jpg.low-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless4_s22.jpg.low-diff.png -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless5_s22.jpg.low-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless5_s22.jpg.low-diff.png -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless6_s22.jpg.low-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless6_s22.jpg.low-diff.png -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless7_s22.jpg.low-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_lossless/lossless7_s22.jpg.low-diff.png -------------------------------------------------------------------------------- /tests/Assets/huffman_sequential/testorig12.jpg.low-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_sequential/testorig12.jpg.low-diff.png -------------------------------------------------------------------------------- /tests/Assets/arithmetic_sequential/yellowcat_arith_restart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/arithmetic_sequential/yellowcat_arith_restart.jpg -------------------------------------------------------------------------------- /tests/Assets/arithmetic_sequential/zackthecat_arith_restart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/arithmetic_sequential/zackthecat_arith_restart.jpg -------------------------------------------------------------------------------- /tests/Assets/arithmetic_sequential/zackthecat_arith.jpg.high.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/arithmetic_sequential/zackthecat_arith.jpg.high.png -------------------------------------------------------------------------------- /tests/Assets/huffman_progressive/yellowcat_progressive_restart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_progressive/yellowcat_progressive_restart.jpg -------------------------------------------------------------------------------- /tests/Assets/arithmetic_progressive/yellowcat_progressive_arith.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/arithmetic_progressive/yellowcat_progressive_arith.jpg -------------------------------------------------------------------------------- /tests/Assets/arithmetic_sequential/zackthecat_arith.jpg.low-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/arithmetic_sequential/zackthecat_arith.jpg.low-diff.png -------------------------------------------------------------------------------- /tests/Assets/arithmetic_sequential/yellowcat_arith_restart.jpg.high.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/arithmetic_sequential/yellowcat_arith_restart.jpg.high.png -------------------------------------------------------------------------------- /tests/Assets/arithmetic_sequential/zackthecat_arith_restart.jpg.high.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/arithmetic_sequential/zackthecat_arith_restart.jpg.high.png -------------------------------------------------------------------------------- /tests/Assets/arithmetic_progressive/yellowcat_progressive_arith.jpg.high.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/arithmetic_progressive/yellowcat_progressive_arith.jpg.high.png -------------------------------------------------------------------------------- /tests/Assets/arithmetic_progressive/yellowcat_progressive_arith_restart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/arithmetic_progressive/yellowcat_progressive_arith_restart.jpg -------------------------------------------------------------------------------- /tests/Assets/arithmetic_sequential/yellowcat_arith_restart.jpg.low-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/arithmetic_sequential/yellowcat_arith_restart.jpg.low-diff.png -------------------------------------------------------------------------------- /tests/Assets/arithmetic_sequential/zackthecat_arith_restart.jpg.low-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/arithmetic_sequential/zackthecat_arith_restart.jpg.low-diff.png -------------------------------------------------------------------------------- /tests/Assets/huffman_progressive/yellowcat_progressive_restart.jpg.high.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_progressive/yellowcat_progressive_restart.jpg.high.png -------------------------------------------------------------------------------- /tests/Assets/arithmetic_progressive/yellowcat_progressive_arith.jpg.low-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/arithmetic_progressive/yellowcat_progressive_arith.jpg.low-diff.png -------------------------------------------------------------------------------- /tests/Assets/huffman_progressive/yellowcat_progressive_restart.jpg.low-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/huffman_progressive/yellowcat_progressive_restart.jpg.low-diff.png -------------------------------------------------------------------------------- /tests/Assets/arithmetic_progressive/yellowcat_progressive_arith_restart.jpg.high.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/arithmetic_progressive/yellowcat_progressive_arith_restart.jpg.high.png -------------------------------------------------------------------------------- /tests/Assets/arithmetic_progressive/yellowcat_progressive_arith_restart.jpg.low-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yigolden/JpegLibrary/HEAD/tests/Assets/arithmetic_progressive/yellowcat_progressive_arith_restart.jpg.low-diff.png -------------------------------------------------------------------------------- /THIRD-PARTY-NOTICES.md: -------------------------------------------------------------------------------- 1 | This application contains materials from third parties, supplied under the following licenses: 2 | 3 | * SixLabors.ImageSharp 4 | Copyright 2018 Six Labors 5 | Licensed under the Apache License, Version 2.0 (https://github.com/SixLabors/ImageSharp/blob/master/LICENSE) 6 | -------------------------------------------------------------------------------- /src/JpegLibrary/JpegMarkerHelper.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace JpegLibrary 4 | { 5 | internal static class JpegMarkerHelper 6 | { 7 | public static bool IsRestartMarker(this JpegMarker marker) 8 | { 9 | return JpegMarker.DefineRestart0 <= marker && marker <= JpegMarker.DefineRestart7; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", 3 | "version": "0.5-alpha", 4 | "publicReleaseRefSpec": [ 5 | "^refs/heads/release/\\d+(?:\\.\\d+)?$" 6 | ], 7 | "cloudBuild": { 8 | "buildNumber": { 9 | "enabled": true 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using BenchmarkDotNet.Running; 3 | 4 | namespace JpegLibrary.Benchmarks 5 | { 6 | internal class Program 7 | { 8 | private static void Main(string[] args) 9 | { 10 | new BenchmarkSwitcher(typeof(Program).GetTypeInfo().Assembly).Run(args); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/JpegOptimize/JpegOptimize.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/JpegLibrary/JpegElementPrecision.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace JpegLibrary 4 | { 5 | /// 6 | /// Element precision of quantization tablse. 7 | /// 8 | public enum JpegElementPrecision : byte 9 | { 10 | /// 11 | /// 8 bit precision. 12 | /// 13 | Precision8Bit = 0, 14 | 15 | /// 16 | /// 12 bit precision. 17 | /// 18 | Precision12Bit = 1, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/JpegLibrary/Utils/SkipLocalsInitAttribute.cs: -------------------------------------------------------------------------------- 1 | #if NO_SKIP_LOCALS_INIT 2 | 3 | namespace System.Runtime.CompilerServices 4 | { 5 | [AttributeUsage(AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event | AttributeTargets.Interface, Inherited = false)] 6 | internal sealed class SkipLocalsInitAttribute : Attribute 7 | { 8 | public SkipLocalsInitAttribute() { } 9 | } 10 | } 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /apps/JpegDecode/JpegDecode.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /apps/JpegEncode/JpegEncode.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 10.0 11 | true 12 | 13 | 14 | -------------------------------------------------------------------------------- /apps/JpegDebugDump/JpegDebugDump.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Benchmarks/SkipLocalsInitAttribute.cs: -------------------------------------------------------------------------------- 1 | [module: System.Runtime.CompilerServices.SkipLocalsInit] 2 | 3 | #if NO_SKIP_LOCALS_INIT 4 | 5 | namespace System.Runtime.CompilerServices 6 | { 7 | [AttributeUsage(AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event | AttributeTargets.Interface, Inherited = false)] 8 | internal sealed class SkipLocalsInitAttribute : Attribute 9 | { 10 | public SkipLocalsInitAttribute() { } 11 | } 12 | } 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/JpegLibrary/Utils/NullableAnnotations.cs: -------------------------------------------------------------------------------- 1 | #if NO_NULLABLE_REFERENCE 2 | 3 | namespace System.Diagnostics.CodeAnalysis 4 | { 5 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] 6 | internal sealed class NotNullWhenAttribute : Attribute 7 | { 8 | public NotNullWhenAttribute(bool returnValue) 9 | { 10 | ReturnValue = returnValue; 11 | } 12 | 13 | public bool ReturnValue { get; } 14 | } 15 | 16 | [AttributeUsage(AttributeTargets.Method, Inherited = false)] 17 | internal sealed class DoesNotReturnAttribute : Attribute 18 | { 19 | public DoesNotReturnAttribute() { } 20 | } 21 | } 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/JpegLibrary/JpegHuffmanDecodingComponent.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace JpegLibrary 4 | { 5 | internal class JpegHuffmanDecodingComponent 6 | { 7 | public int ComponentIndex { get; internal set; } 8 | public byte HorizontalSamplingFactor { get; internal set; } 9 | public byte VerticalSamplingFactor { get; internal set; } 10 | internal int DcPredictor { get; set; } 11 | internal JpegHuffmanDecodingTable? DcTable { get; set; } 12 | internal JpegHuffmanDecodingTable? AcTable { get; set; } 13 | internal JpegQuantizationTable QuantizationTable { get; set; } 14 | internal int HorizontalSubsamplingFactor { get; set; } 15 | internal int VerticalSubsamplingFactor { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/JpegLibrary/JpegTranscodeComponent.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace JpegLibrary 4 | { 5 | internal class JpegTranscodeComponent 6 | { 7 | public int ComponentIndex { get; set; } 8 | public int HorizontalSamplingFactor { get; set; } 9 | public int VerticalSamplingFactor { get; set; } 10 | public JpegHuffmanDecodingTable? DcTable { get; set; } 11 | public JpegHuffmanDecodingTable? AcTable { get; set; } 12 | public JpegHuffmanEncodingTableBuilder? DcTableBuilder { get; set; } 13 | public JpegHuffmanEncodingTableBuilder? AcTableBuilder { get; set; } 14 | public JpegHuffmanEncodingTable? DcEncodingTable { get; set; } 15 | public JpegHuffmanEncodingTable? AcEncodingTable { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/JpegLibrary/JpegBlockOutputWriter.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace JpegLibrary 4 | { 5 | /// 6 | /// A output writer that write spatial block to the destination buffer. 7 | /// 8 | public abstract partial class JpegBlockOutputWriter 9 | { 10 | /// 11 | /// Write a 8x8 spatial block into the destination buffer. 12 | /// 13 | /// The reference to the block that the implementation should read from. 14 | /// The index of the component. 15 | /// The X offset in the image. 16 | /// The Y offset in the image. 17 | public abstract void WriteBlock(ref short blockRef, int componentIndex, int x, int y); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/JpegLibrary/JpegArithmeticDecodingComponent.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace JpegLibrary 4 | { 5 | internal class JpegArithmeticDecodingComponent 6 | { 7 | public int ComponentIndex { get; internal set; } 8 | public byte HorizontalSamplingFactor { get; internal set; } 9 | public byte VerticalSamplingFactor { get; internal set; } 10 | internal int DcPredictor { get; set; } 11 | internal JpegArithmeticDecodingTable? DcTable { get; set; } 12 | internal JpegArithmeticDecodingTable? AcTable { get; set; } 13 | internal JpegQuantizationTable QuantizationTable { get; set; } 14 | internal int HorizontalSubsamplingFactor { get; set; } 15 | internal int VerticalSubsamplingFactor { get; set; } 16 | 17 | internal int DcContext { get; set; } 18 | 19 | internal JpegArithmeticStatistics? DcStatistics { get; set; } 20 | internal JpegArithmeticStatistics? AcStatistics { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/JpegLibrary/JpegArithmeticStatistics.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | 5 | namespace JpegLibrary 6 | { 7 | internal class JpegArithmeticStatistics 8 | { 9 | private readonly bool _dc; 10 | private readonly byte _identifier; 11 | private readonly byte[] _statistics; 12 | 13 | public JpegArithmeticStatistics(bool dc, byte identifier) 14 | { 15 | _dc = dc; 16 | _identifier = identifier; 17 | _statistics = dc ? new byte[64] : new byte[256]; 18 | } 19 | 20 | public bool IsDcStatistics => _dc; 21 | 22 | public byte Identifier => _identifier; 23 | 24 | public ref byte GetReference() 25 | { 26 | return ref _statistics[0]; 27 | } 28 | 29 | public ref byte GetReference(int offset) 30 | { 31 | return ref _statistics[offset]; 32 | } 33 | 34 | public void Reset() 35 | { 36 | _statistics.AsSpan().Clear(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/JpegLibrary/JpegHuffmanEncodingComponent.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace JpegLibrary 4 | { 5 | internal class JpegHuffmanEncodingComponent 6 | { 7 | public int Index { get; internal set; } 8 | public int ComponentIndex { get; internal set; } 9 | public byte HorizontalSamplingFactor { get; internal set; } 10 | public byte VerticalSamplingFactor { get; internal set; } 11 | internal int DcPredictor { get; set; } 12 | internal byte DcTableIdentifier { get; set; } 13 | internal byte AcTableIdentifier { get; set; } 14 | internal JpegHuffmanEncodingTable? DcTable { get; set; } 15 | internal JpegHuffmanEncodingTable? AcTable { get; set; } 16 | internal JpegHuffmanEncodingTableBuilder? DcTableBuilder { get; set; } 17 | internal JpegHuffmanEncodingTableBuilder? AcTableBuilder { get; set; } 18 | internal JpegQuantizationTable QuantizationTable { get; set; } 19 | 20 | internal int HorizontalSubsamplingFactor { get; set; } 21 | internal int VerticalSubsamplingFactor { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/JpegLibrary/JpegBlockInputReader.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace JpegLibrary 4 | { 5 | /// 6 | /// A input reader that read spatial block from the buffer. 7 | /// 8 | public abstract class JpegBlockInputReader 9 | { 10 | /// 11 | /// The width of the image. 12 | /// 13 | public abstract int Width { get; } 14 | 15 | /// 16 | /// The height of the image. 17 | /// 18 | public abstract int Height { get; } 19 | 20 | /// 21 | /// Read a 8x8 spatial block from the source buffer. 22 | /// 23 | /// The reference to the block that the implementation should write to. 24 | /// The index of the component. 25 | /// The X offset in the image. 26 | /// The Y offset in the image. 27 | public abstract void ReadBlock(ref short blockRef, int componentIndex, int x, int y); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Benchmarks/JpegLibrary.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Exe 6 | netcoreapp3.1;net6.0 7 | false 8 | 9 | 10 | 11 | $(DefineConstants);NO_SKIP_LOCALS_INIT 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Resources\HETissueSlide.jpg 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2021 yigolden 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | 6 | 7 | 8 | 9 | 10 | JpegLibrary 11 | YiGolden 12 | Copyright (c) 2019-2021 yigolden 13 | jpeg 14 | MIT 15 | https://github.com/yigolden/JpegLibrary 16 | git 17 | https://github.com/yigolden/JpegLibrary.git 18 | 19 | $(MSBuildThisFileDirectory)build\key.snk 20 | true 21 | 22 | 23 | 24 | 25 | $(NerdbankGitVersioningPackageVersion) 26 | all 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Tests/JpegLibrary.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | netcoreapp3.1 6 | true 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | all 17 | 18 | 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | all 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Benchmarks/NullWriteStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace JpegLibrary.Benchmarks 5 | { 6 | internal sealed class NullWriteStream : Stream 7 | { 8 | public override bool CanRead => false; 9 | 10 | public override bool CanSeek => false; 11 | 12 | public override bool CanWrite => true; 13 | 14 | public override long Length => throw new NotSupportedException(); 15 | 16 | public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } 17 | 18 | public override void Flush() 19 | { 20 | 21 | } 22 | 23 | public override int Read(byte[] buffer, int offset, int count) 24 | { 25 | throw new NotSupportedException(); 26 | } 27 | 28 | public override long Seek(long offset, SeekOrigin origin) 29 | { 30 | throw new NotSupportedException(); 31 | } 32 | 33 | public override void SetLength(long value) 34 | { 35 | throw new NotSupportedException(); 36 | } 37 | 38 | public override void Write(byte[] buffer, int offset, int count) 39 | { 40 | 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JpegLibrary 2 | 3 | JPEG decoder, encoder and optimizer implemented in C#. 4 | 5 | [![Build Status](https://dev.azure.com/jinyi0679/yigolden/_apis/build/status/yigolden.JpegLibrary?branchName=master)](https://dev.azure.com/jinyi0679/yigolden/_build/latest?definitionId=2&branchName=master) 6 | 7 | ## Supported Runtimes 8 | 9 | * .NET Framework 4.6.1+ 10 | * .NET Core 3.1+ 11 | * Runtimes compatible with .NET Standard 2.0 12 | 13 | ## Supported Features 14 | 15 | 16 | ### Decode 17 | * Decode Huffman-coding baseline DCT-based JPEG (SOF0) 18 | * Decode Huffman-coding extended sequential DCT-based JPEG (SOF1) 19 | * Decode Huffman-coding progressive DCT-based JPEG (SOF2) 20 | * Decode Huffman-coding lossless JPEG (SOF3) 21 | * Decode arithmetic-coding sequential DCT-based JPEG (SOF9) 22 | * Decode arithmetic-coding progressive DCT-based JPEG (SOF10) 23 | 24 | See [JpegDecode](https://github.com/yigolden/JpegLibrary/blob/master/apps/JpegDecode/DecodeAction.cs) program for example. 25 | 26 | ### Encode 27 | * Encode Huffman-coding baseline DCT-based JPEG (SOF0) with optimized coding. 28 | 29 | See [JpegEncode](https://github.com/yigolden/JpegLibrary/blob/master/apps/JpegEncode/EncodeAction.cs) program for example. 30 | 31 | ### Optimize 32 | * Optimize an existing baseline image to use optimized Huffman coding. 33 | 34 | See [JpegOptimize](https://github.com/yigolden/JpegLibrary/blob/master/apps/JpegOptimize/OptimizeAction.cs) program for example. 35 | -------------------------------------------------------------------------------- /apps/JpegDecode/Program.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | using System.CommandLine.Builder; 3 | using System.CommandLine.Invocation; 4 | using System.CommandLine.Parsing; 5 | using System.IO; 6 | using System.Threading.Tasks; 7 | 8 | namespace JpegDecode 9 | { 10 | class Program 11 | { 12 | static async Task Main(string[] args) 13 | { 14 | var builder = new CommandLineBuilder(); 15 | 16 | SetupDebugDumpCommand(builder.Command); 17 | 18 | builder.UseDefaults(); 19 | 20 | Parser parser = builder.Build(); 21 | await parser.InvokeAsync(args); 22 | } 23 | 24 | static void SetupDebugDumpCommand(Command command) 25 | { 26 | command.Description = "Decode image from JPEG file."; 27 | 28 | command.AddOption(Output()); 29 | 30 | command.AddArgument(new Argument() 31 | { 32 | Name = "source", 33 | Description = "The JPEG file to decode.", 34 | Arity = ArgumentArity.ExactlyOne 35 | }.ExistingOnly()); 36 | 37 | 38 | command.Handler = CommandHandler.Create(DecodeAction.Decode); 39 | 40 | static Option Output() => 41 | new Option(new[] { "--output", "--out", "-o" }, "Output image file.") 42 | { 43 | Name = "output", 44 | Arity = ArgumentArity.ZeroOrOne 45 | }; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /apps/JpegOptimize/Program.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | using System.CommandLine.Builder; 3 | using System.CommandLine.Invocation; 4 | using System.CommandLine.Parsing; 5 | using System.IO; 6 | using System.Threading.Tasks; 7 | 8 | namespace JpegOptimize 9 | { 10 | class Program 11 | { 12 | static async Task Main(string[] args) 13 | { 14 | var builder = new CommandLineBuilder(); 15 | 16 | SetupOptimizeCommand(builder.Command); 17 | 18 | builder.UseDefaults(); 19 | 20 | Parser parser = builder.Build(); 21 | await parser.InvokeAsync(args); 22 | } 23 | 24 | static void SetupOptimizeCommand(Command command) 25 | { 26 | command.Description = "Optimize a baseline JPEG image for file size."; 27 | 28 | command.AddOption(Output()); 29 | 30 | command.AddArgument(new Argument() 31 | { 32 | Name = "source", 33 | Description = "The JPEG file to optimize.", 34 | Arity = ArgumentArity.ExactlyOne 35 | }.ExistingOnly()); 36 | 37 | 38 | command.Handler = CommandHandler.Create(OptimizeAction.Optimize); 39 | 40 | static Option Output() => 41 | new Option(new[] { "--output", "--out", "-o" }, "Output optimized JPEG file.") 42 | { 43 | Name = "output", 44 | Arity = ArgumentArity.ExactlyOne 45 | }; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Tests/Decoder/HuffmanProgressiveDecodeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Xunit; 5 | 6 | namespace JpegLibrary.Tests.Decoder 7 | { 8 | public class HuffmanProgressiveDecodeTests 9 | { 10 | public static IEnumerable GetTestData() 11 | { 12 | string currentDir = Directory.GetCurrentDirectory(); 13 | yield return new object[] { 14 | Path.Join(currentDir, @"Assets/huffman_progressive/progress.jpg") 15 | }; 16 | yield return new object[] { 17 | Path.Join(currentDir, @"Assets/huffman_progressive/yellowcat_progressive_restart.jpg") 18 | }; 19 | } 20 | 21 | [Theory] 22 | [MemberData(nameof(GetTestData))] 23 | public void TestDecode(string path) 24 | { 25 | byte[] jpegBytes = File.ReadAllBytes(path); 26 | 27 | var decoder = new JpegDecoder(); 28 | decoder.SetInput(jpegBytes); 29 | decoder.Identify(); 30 | 31 | ushort[] buffer = new ushort[decoder.Width * decoder.Height * 4]; 32 | var outputWriter = new JpegExtendingOutputWriter(decoder.Width, decoder.Height, 4, decoder.Precision, buffer); 33 | 34 | decoder.SetOutputWriter(outputWriter); 35 | decoder.Decode(); 36 | 37 | ushort[] reference = ImageHelper.LoadBuffer(path, decoder.Width, decoder.Height, decoder.NumberOfComponents); 38 | 39 | Assert.True(reference.AsSpan().SequenceEqual(buffer)); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /apps/JpegDebugDump/Program.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | using System.CommandLine.Builder; 3 | using System.CommandLine.Invocation; 4 | using System.CommandLine.Parsing; 5 | using System.IO; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace JpegDebugDump 10 | { 11 | internal static class Program 12 | { 13 | static async Task Main(string[] args) 14 | { 15 | var builder = new CommandLineBuilder(); 16 | 17 | SetupDebugDumpCommand(builder.Command); 18 | 19 | builder.UseDefaults(); 20 | 21 | Parser parser = builder.Build(); 22 | await parser.InvokeAsync(args); 23 | } 24 | 25 | static void SetupDebugDumpCommand(Command command) 26 | { 27 | command.Description = "Dump decoded JPEG image components."; 28 | 29 | command.AddOption(Output()); 30 | 31 | command.AddArgument(new Argument() 32 | { 33 | Name = "source", 34 | Description = "The JPEG file to dump.", 35 | Arity = ArgumentArity.ExactlyOne 36 | }.ExistingOnly()); 37 | 38 | 39 | command.Handler = CommandHandler.Create(DebugDumpAction.DebugDump); 40 | 41 | static Option Output() => 42 | new Option(new[] { "--output", "--out", "-o" }, "Output file base path.") 43 | { 44 | Name = "output", 45 | Arity = ArgumentArity.ZeroOrOne 46 | }; 47 | 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Tests/Decoder/ArithmeticProgressiveDecodeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Xunit; 5 | 6 | namespace JpegLibrary.Tests.Decoder 7 | { 8 | public class ArithmeticProgressiveDecodeTests 9 | { 10 | public static IEnumerable GetTestData() 11 | { 12 | string currentDir = Directory.GetCurrentDirectory(); 13 | yield return new object[] { 14 | Path.Join(currentDir, @"Assets/arithmetic_progressive/yellowcat_progressive_arith.jpg") 15 | }; 16 | yield return new object[] { 17 | Path.Join(currentDir, @"Assets/arithmetic_progressive/yellowcat_progressive_arith_restart.jpg") 18 | }; 19 | } 20 | 21 | [Theory] 22 | [MemberData(nameof(GetTestData))] 23 | public void TestDecode(string path) 24 | { 25 | byte[] jpegBytes = File.ReadAllBytes(path); 26 | 27 | var decoder = new JpegDecoder(); 28 | decoder.SetInput(jpegBytes); 29 | decoder.Identify(); 30 | 31 | ushort[] buffer = new ushort[decoder.Width * decoder.Height * 4]; 32 | var outputWriter = new JpegExtendingOutputWriter(decoder.Width, decoder.Height, 4, decoder.Precision, buffer); 33 | 34 | decoder.SetOutputWriter(outputWriter); 35 | decoder.Decode(); 36 | 37 | ushort[] reference = ImageHelper.LoadBuffer(path, decoder.Width, decoder.Height, decoder.NumberOfComponents); 38 | 39 | Assert.True(reference.AsSpan().SequenceEqual(buffer)); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Tests/Decoder/HuffmanSequentialDecodeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Xunit; 5 | 6 | namespace JpegLibrary.Tests.Decoder 7 | { 8 | public class HuffmanSequentialDecodeTests 9 | { 10 | public static IEnumerable GetTestData() 11 | { 12 | string currentDir = Directory.GetCurrentDirectory(); 13 | yield return new object[] { 14 | Path.Join(currentDir, @"Assets/baseline/cramps.jpg"), 15 | }; 16 | yield return new object[] { 17 | Path.Join(currentDir, @"Assets/baseline/lake.jpg"), 18 | }; 19 | yield return new object[] { 20 | Path.Join(currentDir, @"Assets/huffman_sequential/testorig12.jpg"), 21 | }; 22 | } 23 | 24 | [Theory] 25 | [MemberData(nameof(GetTestData))] 26 | public void TestDecode(string path) 27 | { 28 | byte[] jpegBytes = File.ReadAllBytes(path); 29 | 30 | var decoder = new JpegDecoder(); 31 | decoder.SetInput(jpegBytes); 32 | decoder.Identify(); 33 | 34 | ushort[] buffer = new ushort[decoder.Width * decoder.Height * 4]; 35 | var outputWriter = new JpegExtendingOutputWriter(decoder.Width, decoder.Height, 4, decoder.Precision, buffer); 36 | 37 | decoder.SetOutputWriter(outputWriter); 38 | decoder.Decode(); 39 | 40 | ushort[] reference = ImageHelper.LoadBuffer(path, decoder.Width, decoder.Height, decoder.NumberOfComponents); 41 | 42 | Assert.True(reference.AsSpan().SequenceEqual(buffer)); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | true 7 | true 8 | 10.0 9 | true 10 | 11 | 12 | true 13 | true 14 | snupkg 15 | 16 | 17 | 18 | true 19 | true 20 | false 21 | 22 | 23 | true 24 | true 25 | 26 | 27 | true 28 | $(PreviouslyPublishedPackageVersion) 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Tests/Decoder/ArithmeticSequentialDecodeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Xunit; 5 | 6 | namespace JpegLibrary.Tests.Decoder 7 | { 8 | public class ArithmeticSequentialDecodeTests 9 | { 10 | public static IEnumerable GetTestData() 11 | { 12 | string currentDir = Directory.GetCurrentDirectory(); 13 | yield return new object[] { 14 | Path.Join(currentDir, @"Assets/arithmetic_sequential/zackthecat_arith.jpg") 15 | }; 16 | yield return new object[] { 17 | Path.Join(currentDir, @"Assets/arithmetic_sequential/zackthecat_arith_restart.jpg") 18 | }; 19 | yield return new object[] { 20 | Path.Join(currentDir, @"Assets/arithmetic_sequential/yellowcat_arith_restart.jpg") 21 | }; 22 | } 23 | 24 | [Theory] 25 | [MemberData(nameof(GetTestData))] 26 | public void TestDecode(string path) 27 | { 28 | byte[] jpegBytes = File.ReadAllBytes(path); 29 | 30 | var decoder = new JpegDecoder(); 31 | decoder.SetInput(jpegBytes); 32 | decoder.Identify(); 33 | 34 | ushort[] buffer = new ushort[decoder.Width * decoder.Height * 4]; 35 | var outputWriter = new JpegExtendingOutputWriter(decoder.Width, decoder.Height, 4, decoder.Precision, buffer); 36 | 37 | decoder.SetOutputWriter(outputWriter); 38 | decoder.Decode(); 39 | 40 | ushort[] reference = ImageHelper.LoadBuffer(path, decoder.Width, decoder.Height, decoder.NumberOfComponents); 41 | 42 | Assert.True(reference.AsSpan().SequenceEqual(buffer)); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/JpegLibrary/JpegLibrary.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JpegLibrary 5 | JPEG decoder, encoder and optimizer implemented in C#. 6 | net461;netstandard2.0;netstandard2.1;netcoreapp3.1;net6.0 7 | enable 8 | true 9 | 10 | 11 | 12 | $(DefineConstants);NO_MATH_CLAMP;NO_MATHF;NO_FAST_SPAN;NO_NULLABLE_REFERENCE 13 | 14 | 15 | $(DefineConstants);NO_READONLYSEQUENCE_FISTSPAN;NO_BIT_OPERATIONS 16 | 17 | 18 | $(DefineConstants);NO_SKIP_LOCALS_INIT 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/JpegLibrary/JpegHuffmanCanonicalCode.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace JpegLibrary 7 | { 8 | /// 9 | /// Represents a Huffman canonical code. 10 | /// 11 | public struct JpegHuffmanCanonicalCode 12 | { 13 | /// 14 | /// The code value of the symbol. 15 | /// 16 | public ushort Code { get; set; } 17 | 18 | /// 19 | /// The actual symbol. 20 | /// 21 | public byte Symbol { get; set; } 22 | 23 | /// 24 | /// The length of the code. 25 | /// 26 | public byte CodeLength { get; set; } 27 | 28 | /// 29 | public override string ToString() 30 | { 31 | return $"JpegCanonicalCode(Symbol={Symbol},Code={Convert.ToString(Code, 2).PadLeft(CodeLength, '0')},CodeLength={CodeLength})"; 32 | } 33 | } 34 | 35 | internal class JpegHuffmanCanonicalCodeCompareByCodeLen : Comparer 36 | { 37 | public static JpegHuffmanCanonicalCodeCompareByCodeLen Instance { get; } = new JpegHuffmanCanonicalCodeCompareByCodeLen(); 38 | 39 | public override int Compare(JpegHuffmanCanonicalCode x, JpegHuffmanCanonicalCode y) 40 | { 41 | if (x.CodeLength > y.CodeLength) 42 | { 43 | return 1; 44 | } 45 | else if (x.CodeLength < y.CodeLength) 46 | { 47 | return -1; 48 | } 49 | else 50 | { 51 | if (x.Symbol > y.Symbol) 52 | { 53 | return 1; 54 | } 55 | else 56 | { 57 | return -1; 58 | } 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /apps/JpegEncode/JpegBufferInputReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace JpegLibrary.Utils 6 | { 7 | internal sealed class JpegBufferInputReader : JpegBlockInputReader 8 | { 9 | private int _width; 10 | private int _height; 11 | private int _componentCount; 12 | private Memory _buffer; 13 | 14 | public JpegBufferInputReader(int width, int height, int componentCount, Memory buffer) 15 | { 16 | _width = width; 17 | _height = height; 18 | _componentCount = componentCount; 19 | _buffer = buffer; 20 | } 21 | 22 | public override int Width => _width; 23 | 24 | public override int Height => _height; 25 | 26 | public override void ReadBlock(ref short blockRef, int componentIndex, int x, int y) 27 | { 28 | int width = _width; 29 | int componentCount = _componentCount; 30 | 31 | ref byte sourceRef = ref MemoryMarshal.GetReference(MemoryMarshal.AsBytes(_buffer.Span)); 32 | 33 | int blockWidth = Math.Min(width - x, 8); 34 | int blockHeight = Math.Min(_height - y, 8); 35 | 36 | if (blockWidth != 8 || blockHeight != 8) 37 | { 38 | Unsafe.As(ref blockRef) = default; 39 | } 40 | 41 | for (int offsetY = 0; offsetY < blockHeight; offsetY++) 42 | { 43 | int sourceRowOffset = (y + offsetY) * width + x; 44 | ref short destinationRowRef = ref Unsafe.Add(ref blockRef, offsetY * 8); 45 | for (int offsetX = 0; offsetX < blockWidth; offsetX++) 46 | { 47 | Unsafe.Add(ref destinationRowRef, offsetX) = Unsafe.Add(ref sourceRef, (sourceRowOffset + offsetX) * componentCount + componentIndex); 48 | } 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Benchmarks/JpegBufferInputReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace JpegLibrary.Benchmarks 6 | { 7 | internal sealed class JpegBufferInputReader : JpegBlockInputReader 8 | { 9 | private int _width; 10 | private int _height; 11 | private int _componentCount; 12 | private Memory _buffer; 13 | 14 | public JpegBufferInputReader(int width, int height, int componentCount, Memory buffer) 15 | { 16 | _width = width; 17 | _height = height; 18 | _componentCount = componentCount; 19 | _buffer = buffer; 20 | } 21 | 22 | public override int Width => _width; 23 | 24 | public override int Height => _height; 25 | 26 | public override void ReadBlock(ref short blockRef, int componentIndex, int x, int y) 27 | { 28 | int width = _width; 29 | int componentCount = _componentCount; 30 | 31 | ref byte sourceRef = ref MemoryMarshal.GetReference(MemoryMarshal.AsBytes(_buffer.Span)); 32 | 33 | int blockWidth = Math.Min(width - x, 8); 34 | int blockHeight = Math.Min(_height - y, 8); 35 | 36 | if (blockWidth != 8 || blockHeight != 8) 37 | { 38 | Unsafe.As(ref blockRef) = default; 39 | } 40 | 41 | for (int offsetY = 0; offsetY < blockHeight; offsetY++) 42 | { 43 | int sourceRowOffset = (y + offsetY) * width + x; 44 | ref short destinationRowRef = ref Unsafe.Add(ref blockRef, offsetY * 8); 45 | for (int offsetX = 0; offsetX < blockWidth; offsetX++) 46 | { 47 | Unsafe.Add(ref destinationRowRef, offsetX) = Unsafe.Add(ref sourceRef, (sourceRowOffset + offsetX) * componentCount + componentIndex); 48 | } 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /apps/JpegOptimize/OptimizeAction.cs: -------------------------------------------------------------------------------- 1 | using JpegLibrary; 2 | using System; 3 | using System.Buffers; 4 | using System.IO; 5 | using System.Threading.Tasks; 6 | 7 | namespace JpegOptimize 8 | { 9 | public class OptimizeAction 10 | { 11 | public static Task Optimize(FileInfo source, FileInfo output) 12 | { 13 | using var bytes = new MemoryPoolBufferWriter(); 14 | 15 | using (FileStream stream = source.OpenRead()) 16 | { 17 | ReadAllBytes(stream, bytes); 18 | } 19 | 20 | var optimizer = new JpegOptimizer(); 21 | optimizer.SetInput(bytes.GetReadOnlySequence()); 22 | optimizer.Scan(); 23 | 24 | using var writer = new MemoryPoolBufferWriter(); 25 | optimizer.SetOutput(writer); 26 | optimizer.Optimize(); 27 | 28 | using (FileStream stream = output.OpenWrite()) 29 | { 30 | WriteAllBytes(writer.GetReadOnlySequence(), stream); 31 | } 32 | 33 | return Task.FromResult(0); 34 | } 35 | 36 | const int BufferSize = 16384; 37 | 38 | private static void ReadAllBytes(Stream stream, IBufferWriter writer) 39 | { 40 | long length = stream.Length; 41 | while (length > 0) 42 | { 43 | int readSize = (int)Math.Min(length, BufferSize); 44 | Span buffer = writer.GetSpan(readSize); 45 | readSize = stream.Read(buffer); 46 | if (readSize == 0) 47 | { 48 | break; 49 | } 50 | writer.Advance(readSize); 51 | length -= readSize; 52 | } 53 | } 54 | 55 | private static void WriteAllBytes(ReadOnlySequence bytes, Stream stream) 56 | { 57 | foreach (ReadOnlyMemory segment in bytes) 58 | { 59 | stream.Write(segment.Span); 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Tests/Optimizer/OptimizerTests.cs: -------------------------------------------------------------------------------- 1 | using SixLabors.ImageSharp; 2 | using SixLabors.ImageSharp.PixelFormats; 3 | using System; 4 | using System.Buffers; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using Xunit; 8 | 9 | namespace JpegLibrary.Tests.Optimizer 10 | { 11 | public class OptimizerTests 12 | { 13 | public static IEnumerable GetTestData() 14 | { 15 | string currentDir = Directory.GetCurrentDirectory(); 16 | 17 | foreach (bool strip in new bool[] { true, false }) 18 | { 19 | yield return new object[] { 20 | Path.Join(currentDir, @"Assets/baseline/lake.jpg"), 21 | strip 22 | }; 23 | } 24 | } 25 | 26 | [Theory] 27 | [MemberData(nameof(GetTestData))] 28 | public void TestOptimize(string path, bool strip) 29 | { 30 | byte[] jpegBytes = File.ReadAllBytes(path); 31 | 32 | using var refImage = Image.Load(path); 33 | 34 | var optimizer = new JpegOptimizer(); 35 | optimizer.SetInput(jpegBytes); 36 | optimizer.Scan(); 37 | 38 | var buffer = new ArrayBufferWriter(); 39 | optimizer.SetOutput(buffer); 40 | optimizer.Optimize(strip); 41 | 42 | Assert.True(buffer.WrittenCount < jpegBytes.Length); 43 | 44 | using var testImage = Image.Load(buffer.WrittenSpan); 45 | 46 | AssertEqual(refImage, testImage); 47 | } 48 | 49 | private static void AssertEqual(Image image1, Image image2) where T : unmanaged, IPixel 50 | { 51 | Assert.Equal(image1.Width, image2.Width); 52 | Assert.Equal(image1.Height, image2.Height); 53 | 54 | int height = image1.Height; 55 | for (int i = 0; i < height; i++) 56 | { 57 | Assert.True(image1.GetPixelRowSpan(i).SequenceEqual(image2.GetPixelRowSpan(i))); 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /apps/JpegEncode/Program.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | using System.CommandLine.Builder; 3 | using System.CommandLine.Invocation; 4 | using System.CommandLine.Parsing; 5 | using System.IO; 6 | using System.Threading.Tasks; 7 | 8 | namespace JpegEncode 9 | { 10 | class Program 11 | { 12 | static async Task Main(string[] args) 13 | { 14 | var builder = new CommandLineBuilder(); 15 | 16 | SetupEncodeCommand(builder.Command); 17 | 18 | builder.UseDefaults(); 19 | 20 | Parser parser = builder.Build(); 21 | await parser.InvokeAsync(args); 22 | } 23 | 24 | static void SetupEncodeCommand(Command command) 25 | { 26 | command.Description = "Encode JPEG image."; 27 | 28 | command.AddOption(Output()); 29 | command.AddOption(Quality()); 30 | command.AddOption(OptimizeCoding()); 31 | 32 | command.AddArgument(new Argument() 33 | { 34 | Name = "source", 35 | Description = "The file to encode. (BMP, PNG, JPG file)", 36 | Arity = ArgumentArity.ExactlyOne 37 | }.ExistingOnly()); 38 | 39 | command.Handler = CommandHandler.Create(EncodeAction.Encode); 40 | 41 | static Option Output() => 42 | new Option(new[] { "--output", "--out", "-o" }, "Output JPEG file path.") 43 | { 44 | Name = "output", 45 | Arity = ArgumentArity.ExactlyOne 46 | }; 47 | 48 | static Option Quality() => 49 | new Option(new[] { "--quality" }, () => 75, "Output JPEG quality.") 50 | { 51 | Name = "quality", 52 | Arity = ArgumentArity.ExactlyOne 53 | }; 54 | 55 | static Option OptimizeCoding() => 56 | new Option(new[] { "--optimize-coding" }, "Output JPEG quality.") 57 | { 58 | Name = "optimizeCoding", 59 | }; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Benchmarks/NullBufferWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Diagnostics; 4 | 5 | namespace JpegLibrary.Benchmarks 6 | { 7 | internal sealed class NullBufferWriter : IBufferWriter, IDisposable 8 | { 9 | private byte[] _buffer = Array.Empty(); 10 | 11 | public Memory GetMemory(int sizeHint = 0) 12 | { 13 | if (sizeHint <= 0) 14 | { 15 | if (_buffer.Length == 0) 16 | { 17 | _buffer = ArrayPool.Shared.Rent(4096); 18 | } 19 | return _buffer; 20 | } 21 | Debug.Assert(sizeHint > 0); 22 | if (_buffer.Length < sizeHint) 23 | { 24 | if (_buffer.Length != 0) 25 | { 26 | ArrayPool.Shared.Return(_buffer); 27 | } 28 | _buffer = ArrayPool.Shared.Rent(sizeHint); 29 | } 30 | return _buffer; 31 | } 32 | 33 | public Span GetSpan(int sizeHint = 0) 34 | { 35 | if (sizeHint <= 0) 36 | { 37 | if (_buffer.Length == 0) 38 | { 39 | _buffer = ArrayPool.Shared.Rent(4096); 40 | } 41 | return _buffer; 42 | } 43 | Debug.Assert(sizeHint > 0); 44 | if (_buffer.Length < sizeHint) 45 | { 46 | if (_buffer.Length != 0) 47 | { 48 | ArrayPool.Shared.Return(_buffer); 49 | } 50 | _buffer = ArrayPool.Shared.Rent(sizeHint); 51 | } 52 | return _buffer; 53 | } 54 | 55 | public void Advance(int count) 56 | { 57 | if (count > _buffer.Length) 58 | { 59 | throw new InvalidOperationException(); 60 | } 61 | // No op 62 | } 63 | 64 | public void Dispose() 65 | { 66 | if (_buffer.Length > 0) 67 | { 68 | ArrayPool.Shared.Return(_buffer); 69 | } 70 | _buffer = Array.Empty(); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # ASP.NET Core 2 | # Build and test ASP.NET Core projects targeting .NET Core. 3 | # Add steps that run tests, create a NuGet package, deploy, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core 5 | 6 | trigger: 7 | - main 8 | 9 | pool: 10 | vmImage: 'Ubuntu-18.04' 11 | 12 | steps: 13 | - task: UseDotNet@2 14 | displayName: 'Install .NET Core SDK' 15 | inputs: 16 | version: 6.0.x 17 | packageType: sdk 18 | includePreviewVersions: true 19 | performMultiLevelLookup: true 20 | 21 | - task: UseDotNet@2 22 | displayName: 'Install .NET Core 3.1 runtime' 23 | inputs: 24 | version: 3.1.x 25 | packageType: runtime 26 | performMultiLevelLookup: true 27 | 28 | - task: DotNetCoreCLI@2 29 | displayName: 'Restoring Dependencies' 30 | inputs: 31 | command: restore 32 | projects: 'JpegLibrary.sln' 33 | 34 | - task: DotNetCoreCLI@2 35 | displayName: Build (Debug) 36 | inputs: 37 | command: build 38 | projects: 'JpegLibrary.sln' 39 | arguments: '--configuration Debug' 40 | 41 | - task: DotNetCoreCLI@2 42 | displayName: Build (Release) 43 | inputs: 44 | command: build 45 | projects: 'JpegLibrary.sln' 46 | arguments: '--configuration Release' 47 | 48 | - task: DotNetCoreCLI@2 49 | displayName: Run Tests (Debug) 50 | inputs: 51 | command: test 52 | projects: 'tests/*Tests/*.csproj' 53 | arguments: '--configuration Debug' 54 | 55 | - task: DotNetCoreCLI@2 56 | displayName: Run Tests (Release) 57 | inputs: 58 | command: test 59 | projects: 'tests/*Tests/*.csproj' 60 | arguments: '--configuration Release --collect "Code coverage"' 61 | 62 | - task: CopyFiles@2 63 | displayName: Collect Artifacts 64 | inputs: 65 | sourceFolder: 'src' 66 | contents: '**/*.nupkg' 67 | targetFolder: '$(Build.ArtifactStagingDirectory)' 68 | flattenFolders: true 69 | 70 | - task: CopyFiles@2 71 | displayName: Collect Artifacts (Symbols) 72 | inputs: 73 | sourceFolder: 'src' 74 | contents: '**/*.snupkg' 75 | targetFolder: '$(Build.ArtifactStagingDirectory)' 76 | flattenFolders: true 77 | 78 | - task: PublishBuildArtifacts@1 79 | displayName: Publish Artifacts 80 | inputs: 81 | pathtoPublish: '$(Build.ArtifactStagingDirectory)' 82 | artifactName: 'Packages' 83 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Tests/Decoder/HuffmanLosslessDecodeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Xunit; 5 | 6 | namespace JpegLibrary.Tests.Decoder 7 | { 8 | public class HuffmanLosslessDecodeTests 9 | { 10 | public static IEnumerable GetTestData() 11 | { 12 | string currentDir = Directory.GetCurrentDirectory(); 13 | yield return new object[] { 14 | Path.Join(currentDir, @"Assets/huffman_lossless/lossless1_s22.jpg") 15 | }; 16 | yield return new object[] { 17 | Path.Join(currentDir, @"Assets/huffman_lossless/lossless2_s22.jpg"), 18 | }; 19 | yield return new object[] { 20 | Path.Join(currentDir, @"Assets/huffman_lossless/lossless3_s22.jpg"), 21 | }; 22 | yield return new object[] { 23 | Path.Join(currentDir, @"Assets/huffman_lossless/lossless4_s22.jpg"), 24 | }; 25 | yield return new object[] { 26 | Path.Join(currentDir, @"Assets/huffman_lossless/lossless5_s22.jpg"), 27 | }; 28 | yield return new object[] { 29 | Path.Join(currentDir, @"Assets/huffman_lossless/lossless6_s22.jpg"), 30 | }; 31 | yield return new object[] { 32 | Path.Join(currentDir, @"Assets/huffman_lossless/lossless7_s22.jpg"), 33 | }; 34 | } 35 | 36 | [Theory] 37 | [MemberData(nameof(GetTestData))] 38 | public void TestDecode(string path) 39 | { 40 | byte[] jpegBytes = File.ReadAllBytes(path); 41 | 42 | var decoder = new JpegDecoder(); 43 | decoder.SetInput(jpegBytes); 44 | decoder.Identify(); 45 | 46 | ushort[] buffer = new ushort[decoder.Width * decoder.Height * 4]; 47 | var outputWriter = new JpegExtendingOutputWriter(decoder.Width, decoder.Height, 4, decoder.Precision, buffer); 48 | 49 | decoder.SetOutputWriter(outputWriter); 50 | decoder.Decode(); 51 | 52 | ushort[] reference = ImageHelper.LoadBuffer(path, decoder.Width, decoder.Height, decoder.NumberOfComponents); 53 | 54 | Assert.True(reference.AsSpan().SequenceEqual(buffer)); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /apps/JpegDecode/JpegBufferOutputWriter8Bit.cs: -------------------------------------------------------------------------------- 1 | using JpegLibrary; 2 | using System; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace JpegDecode 7 | { 8 | public class JpegBufferOutputWriter8Bit : JpegBlockOutputWriter 9 | { 10 | private int _width; 11 | private int _height; 12 | private int _componentCount; 13 | private Memory _output; 14 | 15 | public JpegBufferOutputWriter8Bit(int width, int height, int componentCount, Memory output) 16 | { 17 | if (output.Length < (width * height * componentCount)) 18 | { 19 | throw new ArgumentException("Destination buffer is too small."); 20 | } 21 | 22 | _width = width; 23 | _height = height; 24 | _componentCount = componentCount; 25 | _output = output; 26 | } 27 | 28 | public override void WriteBlock(ref short blockRef, int componentIndex, int x, int y) 29 | { 30 | int componentCount = _componentCount; 31 | int width = _width; 32 | int height = _height; 33 | 34 | if (x > width || y > _height) 35 | { 36 | return; 37 | } 38 | 39 | int writeWidth = Math.Min(width - x, 8); 40 | int writeHeight = Math.Min(height - y, 8); 41 | 42 | ref byte destinationRef = ref MemoryMarshal.GetReference(_output.Span); 43 | destinationRef = ref Unsafe.Add(ref destinationRef, y * width * componentCount + x * componentCount + componentIndex); 44 | 45 | for (int destY = 0; destY < writeHeight; destY++) 46 | { 47 | ref byte destinationRowRef = ref Unsafe.Add(ref destinationRef, destY * width * componentCount); 48 | for (int destX = 0; destX < writeWidth; destX++) 49 | { 50 | Unsafe.Add(ref destinationRowRef, destX * componentCount) = ClampTo8Bit(Unsafe.Add(ref blockRef, destX)); 51 | } 52 | blockRef = ref Unsafe.Add(ref blockRef, 8); 53 | } 54 | } 55 | 56 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 57 | private static byte ClampTo8Bit(short input) 58 | { 59 | return (byte)Math.Clamp(input, (short)0, (short)255); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Benchmarks/JpegBufferOutputWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace JpegLibrary.Benchmarks 6 | { 7 | internal sealed class JpegBufferOutputWriter : JpegBlockOutputWriter 8 | { 9 | private int _width; 10 | private int _height; 11 | private int _componentCount; 12 | private Memory _output; 13 | 14 | public JpegBufferOutputWriter(int width, int height, int componentsCount, Memory output) 15 | { 16 | if (output.Length < (width * height * componentsCount)) 17 | { 18 | throw new ArgumentException("Destination buffer is too small."); 19 | } 20 | 21 | _width = width; 22 | _height = height; 23 | _componentCount = componentsCount; 24 | _output = output; 25 | } 26 | 27 | public override void WriteBlock(ref short blockRef, int componentIndex, int x, int y) 28 | { 29 | int componentCount = _componentCount; 30 | int width = _width; 31 | int height = _height; 32 | 33 | if (x > width || y > _height) 34 | { 35 | return; 36 | } 37 | 38 | int writeWidth = Math.Min(width - x, 8); 39 | int writeHeight = Math.Min(height - y, 8); 40 | 41 | ref byte destinationRef = ref MemoryMarshal.GetReference(_output.Span); 42 | destinationRef = ref Unsafe.Add(ref destinationRef, y * width * componentCount + x * componentCount + componentIndex); 43 | 44 | for (int destY = 0; destY < writeHeight; destY++) 45 | { 46 | ref byte destinationRowRef = ref Unsafe.Add(ref destinationRef, destY * width * componentCount); 47 | for (int destX = 0; destX < writeWidth; destX++) 48 | { 49 | Unsafe.Add(ref destinationRowRef, destX * componentCount) = ClampTo8Bit(Unsafe.Add(ref blockRef, destX)); 50 | } 51 | blockRef = ref Unsafe.Add(ref blockRef, 8); 52 | } 53 | } 54 | 55 | private static byte ClampTo8Bit(short input) 56 | { 57 | #if NO_MATH_CLAMP 58 | return (byte)Math.Min(Math.Max(input, (short)0), (short)255); 59 | #else 60 | return (byte)Math.Clamp(input, (short)0, (short)255); 61 | #endif 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/JpegLibrary/JpegHuffmanEncodingTableBuilderCollection.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace JpegLibrary 7 | { 8 | internal struct JpegHuffmanEncodingTableBuilderCollection : IDisposable 9 | { 10 | private List? _builders; 11 | 12 | public JpegHuffmanEncodingTableBuilder GetOrCreateTableBuilder(bool isDcTable, byte identifier) 13 | { 14 | byte tableClass = isDcTable ? (byte)0 : (byte)1; 15 | List? builders = _builders; 16 | if (builders is null) 17 | { 18 | builders = _builders = new List(4); 19 | } 20 | foreach (TableBuilderWithIdentifier builder in builders) 21 | { 22 | if (builder.TableClass == tableClass && builder.Identifier == identifier) 23 | { 24 | return builder.TableBuilder; 25 | } 26 | } 27 | var table = new JpegHuffmanEncodingTableBuilder(); 28 | builders.Add(new TableBuilderWithIdentifier(tableClass, identifier, table)); 29 | return table; 30 | } 31 | 32 | public JpegHuffmanEncodingTableCollection BuildTables(bool optimal = false) 33 | { 34 | if (_builders is null) 35 | { 36 | return default; 37 | } 38 | var collection = new JpegHuffmanEncodingTableCollection(); 39 | foreach (TableBuilderWithIdentifier builder in _builders) 40 | { 41 | collection.AddTable(builder.TableClass, builder.Identifier, builder.TableBuilder.Build(optimal)); 42 | } 43 | return collection; 44 | } 45 | 46 | public void Dispose() 47 | { 48 | if (_builders is not null) 49 | { 50 | _builders.Clear(); 51 | _builders = null; 52 | } 53 | } 54 | 55 | 56 | readonly struct TableBuilderWithIdentifier 57 | { 58 | public TableBuilderWithIdentifier(byte tableClass, byte identifier, JpegHuffmanEncodingTableBuilder tableBuilder) 59 | { 60 | TableClass = tableClass; 61 | Identifier = identifier; 62 | TableBuilder = tableBuilder; 63 | } 64 | 65 | public byte TableClass { get; } 66 | public byte Identifier { get; } 67 | public JpegHuffmanEncodingTableBuilder TableBuilder { get; } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /apps/JpegDecode/JpegBufferOutputWriterGreaterThan8Bit.cs: -------------------------------------------------------------------------------- 1 | using JpegLibrary; 2 | using System; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace JpegDecode 7 | { 8 | public class JpegBufferOutputWriterGreaterThan8Bit : JpegBlockOutputWriter 9 | { 10 | private int _width; 11 | private int _height; 12 | private int _shift; 13 | private int _componentCount; 14 | private Memory _output; 15 | 16 | public JpegBufferOutputWriterGreaterThan8Bit(int width, int height, int precision, int componentCount, Memory output) 17 | { 18 | if (output.Length < (width * height * componentCount)) 19 | { 20 | throw new ArgumentException("Destination buffer is too small."); 21 | } 22 | if (precision < 8) 23 | { 24 | throw new ArgumentOutOfRangeException(nameof(precision)); 25 | } 26 | 27 | _width = width; 28 | _height = height; 29 | _shift = precision - 8; 30 | _componentCount = componentCount; 31 | _output = output; 32 | } 33 | 34 | public override void WriteBlock(ref short blockRef, int componentIndex, int x, int y) 35 | { 36 | int componentCount = _componentCount; 37 | int width = _width; 38 | int height = _height; 39 | int shift = _shift; 40 | 41 | if (x > width || y > _height) 42 | { 43 | return; 44 | } 45 | 46 | int writeWidth = Math.Min(width - x, 8); 47 | int writeHeight = Math.Min(height - y, 8); 48 | 49 | ref byte destinationRef = ref MemoryMarshal.GetReference(_output.Span); 50 | destinationRef = ref Unsafe.Add(ref destinationRef, y * width * componentCount + x * componentCount + componentIndex); 51 | 52 | for (int destY = 0; destY < writeHeight; destY++) 53 | { 54 | ref byte destinationRowRef = ref Unsafe.Add(ref destinationRef, destY * width * componentCount); 55 | for (int destX = 0; destX < writeWidth; destX++) 56 | { 57 | Unsafe.Add(ref destinationRowRef, destX * componentCount) = ClampTo8Bit(Unsafe.Add(ref blockRef, destX) >> shift); 58 | } 59 | blockRef = ref Unsafe.Add(ref blockRef, 8); 60 | } 61 | } 62 | 63 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 64 | private static byte ClampTo8Bit(int input) 65 | { 66 | return (byte)Math.Clamp(input, 0, 255); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Benchmarks/DecoderBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using JpegLibrary.ColorConverters; 3 | using SixLabors.ImageSharp; 4 | using SixLabors.ImageSharp.PixelFormats; 5 | using SixLabors.ImageSharp.Processing; 6 | using System; 7 | using System.Buffers; 8 | using System.IO; 9 | using System.Reflection; 10 | using System.Runtime.InteropServices; 11 | 12 | namespace JpegLibrary.Benchmarks 13 | { 14 | [MemoryDiagnoser] 15 | public class DecoderBenchmark 16 | { 17 | private byte[] _inputBytes; 18 | 19 | [GlobalSetup] 20 | public void Setup() 21 | { 22 | var ms = new MemoryStream(); 23 | using (Stream resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("JpegLibrary.Benchmarks.Resources.HETissueSlide.jpg")) 24 | { 25 | resourceStream.CopyTo(ms); 26 | } 27 | ms.Seek(0, SeekOrigin.Begin); 28 | 29 | // Load the image and expand it 30 | using var baseImage = Image.Load(ms); 31 | using var image = new Image(baseImage.Width * 4, baseImage.Height * 4); 32 | image.Mutate(ctx => 33 | { 34 | ctx.DrawImage(baseImage, new Point(0, 0), opacity: 1); 35 | ctx.DrawImage(baseImage, new Point(0, baseImage.Height), opacity: 1); 36 | ctx.DrawImage(baseImage, new Point(baseImage.Width, 0), opacity: 1); 37 | ctx.DrawImage(baseImage, new Point(baseImage.Width, baseImage.Height), opacity: 1); 38 | }); 39 | ms.Seek(0, SeekOrigin.Begin); 40 | ms.SetLength(0); 41 | image.SaveAsJpeg(ms); 42 | _inputBytes = ms.ToArray(); 43 | } 44 | 45 | [Benchmark(Baseline = true)] 46 | public void TestImageSharp() 47 | { 48 | using var image = Image.Load(_inputBytes); 49 | } 50 | 51 | [Benchmark] 52 | public void TestJpegLibrary() 53 | { 54 | var decoder = new JpegDecoder(); 55 | decoder.SetInput(_inputBytes); 56 | decoder.Identify(); 57 | int width = decoder.Width; 58 | int height = decoder.Height; 59 | Rgba32[] rgba = new Rgba32[width * height]; 60 | byte[] ycbcr = ArrayPool.Shared.Rent(3 * rgba.Length); 61 | try 62 | { 63 | var outputWriter = new JpegBufferOutputWriter(decoder.Width, decoder.Height, 3, ycbcr); 64 | decoder.SetOutputWriter(outputWriter); 65 | decoder.Decode(); 66 | 67 | JpegYCbCrToRgbConverter.Shared.ConvertYCbCr8ToRgba32(ycbcr, MemoryMarshal.AsBytes(rgba.AsSpan()), decoder.Width * decoder.Height); 68 | } 69 | finally 70 | { 71 | ArrayPool.Shared.Return(ycbcr); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/JpegLibrary/JpegBlock8x8.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace JpegLibrary 7 | { 8 | /// 9 | /// Represents a 8x8 spatial block. 10 | /// 11 | public unsafe struct JpegBlock8x8 12 | { 13 | private fixed short _data[64]; 14 | 15 | internal void CopyTo(ref JpegBlock8x8F block) 16 | { 17 | ref short srcRef = ref _data[0]; 18 | ref float destRef = ref Unsafe.As(ref block); 19 | for (int i = 0; i < 64; i++) 20 | { 21 | Unsafe.Add(ref destRef, i) = Unsafe.Add(ref srcRef, i); 22 | } 23 | } 24 | 25 | internal void LoadFrom(ref JpegBlock8x8F block) 26 | { 27 | ref short destRef = ref _data[0]; 28 | ref float srcRef = ref Unsafe.As(ref block); 29 | for (int i = 0; i < 64; i++) 30 | { 31 | Unsafe.Add(ref destRef, i) = (short)Unsafe.Add(ref srcRef, i); 32 | } 33 | } 34 | 35 | /// 36 | /// Gets or sets the element at the specified index. 37 | /// 38 | /// The index of the element. 39 | /// The element value. 40 | public short this[int index] 41 | { 42 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 43 | get 44 | { 45 | if ((uint)index >= 64) 46 | { 47 | ThrowArgumentOutOfRangeException(nameof(index)); 48 | } 49 | ref short selfRef = ref Unsafe.As(ref this); 50 | return Unsafe.Add(ref selfRef, index); 51 | } 52 | 53 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 54 | set 55 | { 56 | if ((uint)index >= 64) 57 | { 58 | ThrowArgumentOutOfRangeException(nameof(index)); 59 | } 60 | ref short selfRef = ref Unsafe.As(ref this); 61 | Unsafe.Add(ref selfRef, index) = value; 62 | } 63 | } 64 | 65 | /// 66 | /// Gets or sets the element at the specified position. 67 | /// 68 | /// The row index of the block. 69 | /// The column index of the block. 70 | /// The element value. 71 | public short this[int x, int y] 72 | { 73 | get => this[(y * 8) + x]; 74 | set => this[(y * 8) + x] = value; 75 | } 76 | 77 | private static void ThrowArgumentOutOfRangeException(string paramName) 78 | { 79 | throw new ArgumentOutOfRangeException(paramName); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Benchmarks/JpegRgbaInputReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using SixLabors.ImageSharp.PixelFormats; 5 | 6 | namespace JpegLibrary.Benchmarks 7 | { 8 | internal sealed class JpegRgbaInputReader : JpegBlockInputReader 9 | { 10 | private readonly int _width; 11 | private readonly int _height; 12 | private readonly Memory _buffer; 13 | 14 | public JpegRgbaInputReader(int width, int height, Memory buffer) 15 | { 16 | _width = width; 17 | _height = height; 18 | _buffer = buffer; 19 | } 20 | 21 | public override int Width => _width; 22 | 23 | public override int Height => _height; 24 | 25 | public override void ReadBlock(ref short blockRef, int componentIndex, int x, int y) 26 | { 27 | int width = _width; 28 | 29 | Span sourceSpan = MemoryMarshal.AsBytes(_buffer.Span); 30 | Span componentSpan = stackalloc short[8]; 31 | 32 | int blockWidth = Math.Min(width - x, 8); 33 | int blockHeight = Math.Min(_height - y, 8); 34 | 35 | if (blockWidth != 8 || blockHeight != 8) 36 | { 37 | Unsafe.As(ref blockRef) = default; 38 | } 39 | 40 | switch (componentIndex) 41 | { 42 | case 0: 43 | for (int offsetY = 0; offsetY < blockHeight; offsetY++) 44 | { 45 | int sourceRowOffset = (y + offsetY) * width + x; 46 | ref short destinationRowRef = ref Unsafe.Add(ref blockRef, offsetY * 8); 47 | 48 | JpegRgbToYCbCrComponentConverter.Shared.ConvertRgba32ToYComponent(sourceSpan.Slice(4 * sourceRowOffset), ref destinationRowRef, blockWidth); 49 | } 50 | break; 51 | case 1: 52 | for (int offsetY = 0; offsetY < blockHeight; offsetY++) 53 | { 54 | int sourceRowOffset = (y + offsetY) * width + x; 55 | ref short destinationRowRef = ref Unsafe.Add(ref blockRef, offsetY * 8); 56 | 57 | JpegRgbToYCbCrComponentConverter.Shared.ConvertRgba32ToCbComponent(sourceSpan.Slice(4 * sourceRowOffset), ref destinationRowRef, blockWidth); 58 | } 59 | break; 60 | case 2: 61 | for (int offsetY = 0; offsetY < blockHeight; offsetY++) 62 | { 63 | int sourceRowOffset = (y + offsetY) * width + x; 64 | ref short destinationRowRef = ref Unsafe.Add(ref blockRef, offsetY * 8); 65 | 66 | JpegRgbToYCbCrComponentConverter.Shared.ConvertRgba32ToCrComponent(sourceSpan.Slice(4 * sourceRowOffset), ref destinationRowRef, blockWidth); 67 | } 68 | break; 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /apps/JpegEncode/EncodeAction.cs: -------------------------------------------------------------------------------- 1 | using JpegLibrary; 2 | using JpegLibrary.ColorConverters; 3 | using JpegLibrary.Utils; 4 | using SixLabors.ImageSharp; 5 | using SixLabors.ImageSharp.Advanced; 6 | using SixLabors.ImageSharp.PixelFormats; 7 | using System; 8 | using System.Buffers; 9 | using System.IO; 10 | using System.Runtime.InteropServices; 11 | using System.Threading.Tasks; 12 | 13 | namespace JpegEncode 14 | { 15 | public class EncodeAction 16 | { 17 | public static Task Encode(FileInfo source, FileInfo output, int quality, bool optimizeCoding) 18 | { 19 | if (quality <= 0 || quality > 100) 20 | { 21 | throw new ArgumentOutOfRangeException(nameof(quality)); 22 | } 23 | 24 | Image image; 25 | using (FileStream stream = source.OpenRead()) 26 | { 27 | image = Image.Load(stream); 28 | } 29 | 30 | // Convert RGB to YCbCr 31 | byte[] ycbcr = new byte[image.Width * image.Height * 3]; 32 | for (int i = 0; i < image.Height; i++) 33 | { 34 | JpegRgbToYCbCrConverter.Shared.ConvertRgb24ToYCbCr8(MemoryMarshal.AsBytes(image.GetPixelRowSpan(i)), ycbcr.AsSpan(3 * image.Width * i, 3 * image.Width), image.Width); 35 | } 36 | 37 | var encoder = new JpegEncoder(); 38 | encoder.SetQuantizationTable(JpegStandardQuantizationTable.ScaleByQuality(JpegStandardQuantizationTable.GetLuminanceTable(JpegElementPrecision.Precision8Bit, 0), quality)); 39 | encoder.SetQuantizationTable(JpegStandardQuantizationTable.ScaleByQuality(JpegStandardQuantizationTable.GetChrominanceTable(JpegElementPrecision.Precision8Bit, 1), quality)); 40 | if (optimizeCoding) 41 | { 42 | encoder.SetHuffmanTable(true, 0); 43 | encoder.SetHuffmanTable(false, 0); 44 | encoder.SetHuffmanTable(true, 1); 45 | encoder.SetHuffmanTable(false, 1); 46 | } 47 | else 48 | { 49 | encoder.SetHuffmanTable(true, 0, JpegStandardHuffmanEncodingTable.GetLuminanceDCTable()); 50 | encoder.SetHuffmanTable(false, 0, JpegStandardHuffmanEncodingTable.GetLuminanceACTable()); 51 | encoder.SetHuffmanTable(true, 1, JpegStandardHuffmanEncodingTable.GetChrominanceDCTable()); 52 | encoder.SetHuffmanTable(false, 1, JpegStandardHuffmanEncodingTable.GetChrominanceACTable()); 53 | } 54 | encoder.AddComponent(1, 0, 0, 0, 2, 2); // Y component 55 | encoder.AddComponent(2, 1, 1, 1, 1, 1); // Cb component 56 | encoder.AddComponent(3, 1, 1, 1, 1, 1); // Cr component 57 | 58 | encoder.SetInputReader(new JpegBufferInputReader(image.Width, image.Height, 3, ycbcr)); 59 | 60 | var writer = new ArrayBufferWriter(); 61 | encoder.SetOutput(writer); 62 | 63 | encoder.Encode(); 64 | 65 | using (FileStream stream = output.OpenWrite()) 66 | { 67 | stream.Write(writer.WrittenSpan); 68 | } 69 | 70 | return Task.FromResult(0); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/JpegLibrary/ScanDecoder/JpegScanDecoder.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Diagnostics; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.IO; 7 | using System.Runtime.CompilerServices; 8 | using System.Runtime.InteropServices; 9 | 10 | namespace JpegLibrary.ScanDecoder 11 | { 12 | internal abstract class JpegScanDecoder : IDisposable 13 | { 14 | public abstract void ProcessScan(ref JpegReader reader, JpegScanHeader scanHeader); 15 | 16 | public abstract void Dispose(); 17 | 18 | public static JpegScanDecoder? Create(JpegMarker sofMarker, JpegDecoder decoder, JpegFrameHeader header) 19 | { 20 | switch (sofMarker) 21 | { 22 | case JpegMarker.StartOfFrame0: 23 | case JpegMarker.StartOfFrame1: 24 | return new JpegHuffmanBaselineScanDecoder(decoder, header); 25 | case JpegMarker.StartOfFrame2: 26 | return new JpegHuffmanProgressiveScanDecoder(decoder, header); 27 | case JpegMarker.StartOfFrame3: 28 | return new JpegHuffmanLosslessScanDecoder(decoder, header); 29 | case JpegMarker.StartOfFrame9: 30 | return new JpegArithmeticSequentialScanDecoder(decoder, header); 31 | case JpegMarker.StartOfFrame10: 32 | return new JpegArithmeticProgressiveScanDecoder(decoder, header); 33 | default: 34 | return null; 35 | } 36 | } 37 | 38 | [DoesNotReturn] 39 | protected static void ThrowInvalidDataException(string message) 40 | { 41 | throw new InvalidDataException("Failed to decode JPEG data. " + message); 42 | } 43 | 44 | [DoesNotReturn] 45 | protected static void ThrowInvalidDataException(int offset, string message) 46 | { 47 | throw new InvalidDataException($"Failed to decode JPEG data at offset {offset}. {message}"); 48 | } 49 | 50 | protected static void DequantizeBlockAndUnZigZag(JpegQuantizationTable quantizationTable, ref JpegBlock8x8 input, ref JpegBlock8x8F output) 51 | { 52 | Debug.Assert(!quantizationTable.IsEmpty); 53 | 54 | ref ushort elementRef = ref MemoryMarshal.GetReference(quantizationTable.Elements); 55 | ref short sourceRef = ref Unsafe.As(ref input); 56 | ref float destinationRef = ref Unsafe.As(ref output); 57 | 58 | for (int i = 0; i < 64; i++) 59 | { 60 | Unsafe.Add(ref destinationRef, JpegZigZag.InternalBufferIndexToBlock(i)) = Unsafe.Add(ref elementRef, i) * Unsafe.Add(ref sourceRef, i); 61 | } 62 | } 63 | 64 | protected static void ShiftDataLevel(ref JpegBlock8x8F source, ref JpegBlock8x8 destination, int levelShift) 65 | { 66 | ref float sourceRef = ref Unsafe.As(ref source); 67 | ref short destinationRef = ref Unsafe.As(ref destination); 68 | 69 | for (int i = 0; i < 64; i++) 70 | { 71 | Unsafe.Add(ref destinationRef, i) = (short)(JpegMathHelper.RoundToInt32(Unsafe.Add(ref sourceRef, i)) + levelShift); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless1_s22.jpg.txt: -------------------------------------------------------------------------------- 1 | 2 | JPEGsnoop 1.8.0a by Calvin Hass 3 | http://www.impulseadventure.com/photo/ 4 | ------------------------------------- 5 | 6 | Filename: [C:\Users\jinyi\Documents\Repos\JpegLibrary\tests\Assets\huffman_lossless\lossless1_s22.jpg] 7 | Filesize: [15344] Bytes 8 | 9 | Start Offset: 0x00000000 10 | *** Marker: SOI (xFFD8) *** 11 | OFFSET: 0x00000000 12 | 13 | *** Marker: SOF3 (Lossless Process, Huffman) (xFFC3) *** 14 | OFFSET: 0x00000002 15 | Frame header length = 17 16 | Precision = 8 17 | Number of Lines = 128 18 | Samples per Line = 128 19 | Image Size = 128 x 128 20 | Raw Image Orientation = Landscape 21 | Number of Img components = 3 22 | Component[1]: ID=0x01, Samp Fac=0x22 (Subsamp 1 x 1), Quant Tbl Sel=0x00 (Lum: Y) 23 | Component[2]: ID=0x02, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cb) 24 | Component[3]: ID=0x03, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cr) 25 | 26 | *** Marker: DHT (Define Huffman Table) (xFFC4) *** 27 | OFFSET: 0x00000015 28 | Huffman table length = 51 29 | ---- 30 | Destination ID = 0 31 | Class = 0 (DC / Lossless Table) 32 | Codes of length 01 bits (000 total): 33 | Codes of length 02 bits (002 total): 02 03 34 | Codes of length 03 bits (003 total): 01 04 05 35 | Codes of length 04 bits (001 total): 00 36 | Codes of length 05 bits (001 total): 06 37 | Codes of length 06 bits (001 total): 07 38 | Codes of length 07 bits (000 total): 39 | Codes of length 08 bits (000 total): 40 | Codes of length 09 bits (000 total): 41 | Codes of length 10 bits (000 total): 42 | Codes of length 11 bits (000 total): 43 | Codes of length 12 bits (000 total): 44 | Codes of length 13 bits (000 total): 45 | Codes of length 14 bits (000 total): 46 | Codes of length 15 bits (000 total): 47 | Codes of length 16 bits (000 total): 48 | Total number of codes: 008 49 | 50 | ---- 51 | Destination ID = 1 52 | Class = 0 (DC / Lossless Table) 53 | Codes of length 01 bits (000 total): 54 | Codes of length 02 bits (003 total): 00 01 02 55 | Codes of length 03 bits (001 total): 03 56 | Codes of length 04 bits (001 total): 04 57 | Codes of length 05 bits (001 total): 05 58 | Codes of length 06 bits (001 total): 06 59 | Codes of length 07 bits (000 total): 60 | Codes of length 08 bits (000 total): 61 | Codes of length 09 bits (000 total): 62 | Codes of length 10 bits (000 total): 63 | Codes of length 11 bits (000 total): 64 | Codes of length 12 bits (000 total): 65 | Codes of length 13 bits (000 total): 66 | Codes of length 14 bits (000 total): 67 | Codes of length 15 bits (000 total): 68 | Codes of length 16 bits (000 total): 69 | Total number of codes: 007 70 | 71 | 72 | *** Marker: SOS (Start of Scan) (xFFDA) *** 73 | OFFSET: 0x0000004A 74 | Scan header length = 12 75 | Number of img components = 3 76 | Component[1]: selector=0x01, table=0(DC),0(AC) 77 | Component[2]: selector=0x02, table=1(DC),0(AC) 78 | Component[3]: selector=0x03, table=1(DC),0(AC) 79 | Spectral selection = 1 .. 0 80 | Successive approximation = 0x00 81 | 82 | NOTE: Scan parsing doesn't support this SOF mode. 83 | 84 | *** Marker: EOI (End of Image) (xFFD9) *** 85 | OFFSET: 0x00003BEE 86 | 87 | Skipping compression signature search as no DQT 88 | -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless2_s22.jpg.txt: -------------------------------------------------------------------------------- 1 | 2 | JPEGsnoop 1.8.0a by Calvin Hass 3 | http://www.impulseadventure.com/photo/ 4 | ------------------------------------- 5 | 6 | Filename: [C:\Users\jinyi\Documents\Repos\JpegLibrary\tests\Assets\huffman_lossless\lossless2_s22.jpg] 7 | Filesize: [14222] Bytes 8 | 9 | Start Offset: 0x00000000 10 | *** Marker: SOI (xFFD8) *** 11 | OFFSET: 0x00000000 12 | 13 | *** Marker: SOF3 (Lossless Process, Huffman) (xFFC3) *** 14 | OFFSET: 0x00000002 15 | Frame header length = 17 16 | Precision = 8 17 | Number of Lines = 128 18 | Samples per Line = 128 19 | Image Size = 128 x 128 20 | Raw Image Orientation = Landscape 21 | Number of Img components = 3 22 | Component[1]: ID=0x01, Samp Fac=0x22 (Subsamp 1 x 1), Quant Tbl Sel=0x00 (Lum: Y) 23 | Component[2]: ID=0x02, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cb) 24 | Component[3]: ID=0x03, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cr) 25 | 26 | *** Marker: DHT (Define Huffman Table) (xFFC4) *** 27 | OFFSET: 0x00000015 28 | Huffman table length = 51 29 | ---- 30 | Destination ID = 0 31 | Class = 0 (DC / Lossless Table) 32 | Codes of length 01 bits (000 total): 33 | Codes of length 02 bits (002 total): 02 03 34 | Codes of length 03 bits (003 total): 01 04 05 35 | Codes of length 04 bits (001 total): 00 36 | Codes of length 05 bits (001 total): 06 37 | Codes of length 06 bits (001 total): 07 38 | Codes of length 07 bits (000 total): 39 | Codes of length 08 bits (000 total): 40 | Codes of length 09 bits (000 total): 41 | Codes of length 10 bits (000 total): 42 | Codes of length 11 bits (000 total): 43 | Codes of length 12 bits (000 total): 44 | Codes of length 13 bits (000 total): 45 | Codes of length 14 bits (000 total): 46 | Codes of length 15 bits (000 total): 47 | Codes of length 16 bits (000 total): 48 | Total number of codes: 008 49 | 50 | ---- 51 | Destination ID = 1 52 | Class = 0 (DC / Lossless Table) 53 | Codes of length 01 bits (001 total): 00 54 | Codes of length 02 bits (001 total): 01 55 | Codes of length 03 bits (001 total): 02 56 | Codes of length 04 bits (001 total): 03 57 | Codes of length 05 bits (001 total): 04 58 | Codes of length 06 bits (001 total): 05 59 | Codes of length 07 bits (001 total): 06 60 | Codes of length 08 bits (000 total): 61 | Codes of length 09 bits (000 total): 62 | Codes of length 10 bits (000 total): 63 | Codes of length 11 bits (000 total): 64 | Codes of length 12 bits (000 total): 65 | Codes of length 13 bits (000 total): 66 | Codes of length 14 bits (000 total): 67 | Codes of length 15 bits (000 total): 68 | Codes of length 16 bits (000 total): 69 | Total number of codes: 007 70 | 71 | 72 | *** Marker: SOS (Start of Scan) (xFFDA) *** 73 | OFFSET: 0x0000004A 74 | Scan header length = 12 75 | Number of img components = 3 76 | Component[1]: selector=0x01, table=0(DC),0(AC) 77 | Component[2]: selector=0x02, table=1(DC),0(AC) 78 | Component[3]: selector=0x03, table=1(DC),0(AC) 79 | Spectral selection = 2 .. 0 80 | Successive approximation = 0x00 81 | 82 | NOTE: Scan parsing doesn't support this SOF mode. 83 | 84 | *** Marker: EOI (End of Image) (xFFD9) *** 85 | OFFSET: 0x0000378C 86 | 87 | Skipping compression signature search as no DQT 88 | -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless3_s22.jpg.txt: -------------------------------------------------------------------------------- 1 | 2 | JPEGsnoop 1.8.0a by Calvin Hass 3 | http://www.impulseadventure.com/photo/ 4 | ------------------------------------- 5 | 6 | Filename: [C:\Users\jinyi\Documents\Repos\JpegLibrary\tests\Assets\huffman_lossless\lossless3_s22.jpg] 7 | Filesize: [16621] Bytes 8 | 9 | Start Offset: 0x00000000 10 | *** Marker: SOI (xFFD8) *** 11 | OFFSET: 0x00000000 12 | 13 | *** Marker: SOF3 (Lossless Process, Huffman) (xFFC3) *** 14 | OFFSET: 0x00000002 15 | Frame header length = 17 16 | Precision = 8 17 | Number of Lines = 128 18 | Samples per Line = 128 19 | Image Size = 128 x 128 20 | Raw Image Orientation = Landscape 21 | Number of Img components = 3 22 | Component[1]: ID=0x01, Samp Fac=0x22 (Subsamp 1 x 1), Quant Tbl Sel=0x00 (Lum: Y) 23 | Component[2]: ID=0x02, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cb) 24 | Component[3]: ID=0x03, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cr) 25 | 26 | *** Marker: DHT (Define Huffman Table) (xFFC4) *** 27 | OFFSET: 0x00000015 28 | Huffman table length = 51 29 | ---- 30 | Destination ID = 0 31 | Class = 0 (DC / Lossless Table) 32 | Codes of length 01 bits (000 total): 33 | Codes of length 02 bits (002 total): 03 04 34 | Codes of length 03 bits (003 total): 01 02 05 35 | Codes of length 04 bits (001 total): 06 36 | Codes of length 05 bits (001 total): 00 37 | Codes of length 06 bits (001 total): 07 38 | Codes of length 07 bits (000 total): 39 | Codes of length 08 bits (000 total): 40 | Codes of length 09 bits (000 total): 41 | Codes of length 10 bits (000 total): 42 | Codes of length 11 bits (000 total): 43 | Codes of length 12 bits (000 total): 44 | Codes of length 13 bits (000 total): 45 | Codes of length 14 bits (000 total): 46 | Codes of length 15 bits (000 total): 47 | Codes of length 16 bits (000 total): 48 | Total number of codes: 008 49 | 50 | ---- 51 | Destination ID = 1 52 | Class = 0 (DC / Lossless Table) 53 | Codes of length 01 bits (000 total): 54 | Codes of length 02 bits (003 total): 01 02 03 55 | Codes of length 03 bits (001 total): 00 56 | Codes of length 04 bits (001 total): 04 57 | Codes of length 05 bits (001 total): 05 58 | Codes of length 06 bits (001 total): 06 59 | Codes of length 07 bits (000 total): 60 | Codes of length 08 bits (000 total): 61 | Codes of length 09 bits (000 total): 62 | Codes of length 10 bits (000 total): 63 | Codes of length 11 bits (000 total): 64 | Codes of length 12 bits (000 total): 65 | Codes of length 13 bits (000 total): 66 | Codes of length 14 bits (000 total): 67 | Codes of length 15 bits (000 total): 68 | Codes of length 16 bits (000 total): 69 | Total number of codes: 007 70 | 71 | 72 | *** Marker: SOS (Start of Scan) (xFFDA) *** 73 | OFFSET: 0x0000004A 74 | Scan header length = 12 75 | Number of img components = 3 76 | Component[1]: selector=0x01, table=0(DC),0(AC) 77 | Component[2]: selector=0x02, table=1(DC),0(AC) 78 | Component[3]: selector=0x03, table=1(DC),0(AC) 79 | Spectral selection = 3 .. 0 80 | Successive approximation = 0x00 81 | 82 | NOTE: Scan parsing doesn't support this SOF mode. 83 | 84 | *** Marker: EOI (End of Image) (xFFD9) *** 85 | OFFSET: 0x000040EB 86 | 87 | Skipping compression signature search as no DQT 88 | -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless4_s22.jpg.txt: -------------------------------------------------------------------------------- 1 | 2 | JPEGsnoop 1.8.0a by Calvin Hass 3 | http://www.impulseadventure.com/photo/ 4 | ------------------------------------- 5 | 6 | Filename: [C:\Users\jinyi\Documents\Repos\JpegLibrary\tests\Assets\huffman_lossless\lossless4_s22.jpg] 7 | Filesize: [12174] Bytes 8 | 9 | Start Offset: 0x00000000 10 | *** Marker: SOI (xFFD8) *** 11 | OFFSET: 0x00000000 12 | 13 | *** Marker: SOF3 (Lossless Process, Huffman) (xFFC3) *** 14 | OFFSET: 0x00000002 15 | Frame header length = 17 16 | Precision = 8 17 | Number of Lines = 128 18 | Samples per Line = 128 19 | Image Size = 128 x 128 20 | Raw Image Orientation = Landscape 21 | Number of Img components = 3 22 | Component[1]: ID=0x01, Samp Fac=0x22 (Subsamp 1 x 1), Quant Tbl Sel=0x00 (Lum: Y) 23 | Component[2]: ID=0x02, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cb) 24 | Component[3]: ID=0x03, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cr) 25 | 26 | *** Marker: DHT (Define Huffman Table) (xFFC4) *** 27 | OFFSET: 0x00000015 28 | Huffman table length = 51 29 | ---- 30 | Destination ID = 0 31 | Class = 0 (DC / Lossless Table) 32 | Codes of length 01 bits (000 total): 33 | Codes of length 02 bits (002 total): 00 01 34 | Codes of length 03 bits (003 total): 02 03 04 35 | Codes of length 04 bits (001 total): 05 36 | Codes of length 05 bits (001 total): 06 37 | Codes of length 06 bits (001 total): 07 38 | Codes of length 07 bits (000 total): 39 | Codes of length 08 bits (000 total): 40 | Codes of length 09 bits (000 total): 41 | Codes of length 10 bits (000 total): 42 | Codes of length 11 bits (000 total): 43 | Codes of length 12 bits (000 total): 44 | Codes of length 13 bits (000 total): 45 | Codes of length 14 bits (000 total): 46 | Codes of length 15 bits (000 total): 47 | Codes of length 16 bits (000 total): 48 | Total number of codes: 008 49 | 50 | ---- 51 | Destination ID = 1 52 | Class = 0 (DC / Lossless Table) 53 | Codes of length 01 bits (001 total): 00 54 | Codes of length 02 bits (001 total): 01 55 | Codes of length 03 bits (001 total): 02 56 | Codes of length 04 bits (001 total): 03 57 | Codes of length 05 bits (001 total): 04 58 | Codes of length 06 bits (001 total): 05 59 | Codes of length 07 bits (001 total): 06 60 | Codes of length 08 bits (000 total): 61 | Codes of length 09 bits (000 total): 62 | Codes of length 10 bits (000 total): 63 | Codes of length 11 bits (000 total): 64 | Codes of length 12 bits (000 total): 65 | Codes of length 13 bits (000 total): 66 | Codes of length 14 bits (000 total): 67 | Codes of length 15 bits (000 total): 68 | Codes of length 16 bits (000 total): 69 | Total number of codes: 007 70 | 71 | 72 | *** Marker: SOS (Start of Scan) (xFFDA) *** 73 | OFFSET: 0x0000004A 74 | Scan header length = 12 75 | Number of img components = 3 76 | Component[1]: selector=0x01, table=0(DC),0(AC) 77 | Component[2]: selector=0x02, table=1(DC),0(AC) 78 | Component[3]: selector=0x03, table=1(DC),0(AC) 79 | Spectral selection = 4 .. 0 80 | Successive approximation = 0x00 81 | 82 | NOTE: Scan parsing doesn't support this SOF mode. 83 | 84 | *** Marker: EOI (End of Image) (xFFD9) *** 85 | OFFSET: 0x00002F8C 86 | 87 | Skipping compression signature search as no DQT 88 | -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless5_s22.jpg.txt: -------------------------------------------------------------------------------- 1 | 2 | JPEGsnoop 1.8.0a by Calvin Hass 3 | http://www.impulseadventure.com/photo/ 4 | ------------------------------------- 5 | 6 | Filename: [C:\Users\jinyi\Documents\Repos\JpegLibrary\tests\Assets\huffman_lossless\lossless5_s22.jpg] 7 | Filesize: [13513] Bytes 8 | 9 | Start Offset: 0x00000000 10 | *** Marker: SOI (xFFD8) *** 11 | OFFSET: 0x00000000 12 | 13 | *** Marker: SOF3 (Lossless Process, Huffman) (xFFC3) *** 14 | OFFSET: 0x00000002 15 | Frame header length = 17 16 | Precision = 8 17 | Number of Lines = 128 18 | Samples per Line = 128 19 | Image Size = 128 x 128 20 | Raw Image Orientation = Landscape 21 | Number of Img components = 3 22 | Component[1]: ID=0x01, Samp Fac=0x22 (Subsamp 1 x 1), Quant Tbl Sel=0x00 (Lum: Y) 23 | Component[2]: ID=0x02, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cb) 24 | Component[3]: ID=0x03, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cr) 25 | 26 | *** Marker: DHT (Define Huffman Table) (xFFC4) *** 27 | OFFSET: 0x00000015 28 | Huffman table length = 51 29 | ---- 30 | Destination ID = 0 31 | Class = 0 (DC / Lossless Table) 32 | Codes of length 01 bits (000 total): 33 | Codes of length 02 bits (002 total): 01 02 34 | Codes of length 03 bits (003 total): 00 03 04 35 | Codes of length 04 bits (001 total): 05 36 | Codes of length 05 bits (001 total): 06 37 | Codes of length 06 bits (001 total): 07 38 | Codes of length 07 bits (000 total): 39 | Codes of length 08 bits (000 total): 40 | Codes of length 09 bits (000 total): 41 | Codes of length 10 bits (000 total): 42 | Codes of length 11 bits (000 total): 43 | Codes of length 12 bits (000 total): 44 | Codes of length 13 bits (000 total): 45 | Codes of length 14 bits (000 total): 46 | Codes of length 15 bits (000 total): 47 | Codes of length 16 bits (000 total): 48 | Total number of codes: 008 49 | 50 | ---- 51 | Destination ID = 1 52 | Class = 0 (DC / Lossless Table) 53 | Codes of length 01 bits (001 total): 01 54 | Codes of length 02 bits (001 total): 00 55 | Codes of length 03 bits (001 total): 02 56 | Codes of length 04 bits (001 total): 03 57 | Codes of length 05 bits (001 total): 04 58 | Codes of length 06 bits (001 total): 05 59 | Codes of length 07 bits (001 total): 06 60 | Codes of length 08 bits (000 total): 61 | Codes of length 09 bits (000 total): 62 | Codes of length 10 bits (000 total): 63 | Codes of length 11 bits (000 total): 64 | Codes of length 12 bits (000 total): 65 | Codes of length 13 bits (000 total): 66 | Codes of length 14 bits (000 total): 67 | Codes of length 15 bits (000 total): 68 | Codes of length 16 bits (000 total): 69 | Total number of codes: 007 70 | 71 | 72 | *** Marker: SOS (Start of Scan) (xFFDA) *** 73 | OFFSET: 0x0000004A 74 | Scan header length = 12 75 | Number of img components = 3 76 | Component[1]: selector=0x01, table=0(DC),0(AC) 77 | Component[2]: selector=0x02, table=1(DC),0(AC) 78 | Component[3]: selector=0x03, table=1(DC),0(AC) 79 | Spectral selection = 5 .. 0 80 | Successive approximation = 0x00 81 | 82 | NOTE: Scan parsing doesn't support this SOF mode. 83 | 84 | *** Marker: EOI (End of Image) (xFFD9) *** 85 | OFFSET: 0x000034C7 86 | 87 | Skipping compression signature search as no DQT 88 | -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless6_s22.jpg.txt: -------------------------------------------------------------------------------- 1 | 2 | JPEGsnoop 1.8.0a by Calvin Hass 3 | http://www.impulseadventure.com/photo/ 4 | ------------------------------------- 5 | 6 | Filename: [C:\Users\jinyi\Documents\Repos\JpegLibrary\tests\Assets\huffman_lossless\lossless6_s22.jpg] 7 | Filesize: [12821] Bytes 8 | 9 | Start Offset: 0x00000000 10 | *** Marker: SOI (xFFD8) *** 11 | OFFSET: 0x00000000 12 | 13 | *** Marker: SOF3 (Lossless Process, Huffman) (xFFC3) *** 14 | OFFSET: 0x00000002 15 | Frame header length = 17 16 | Precision = 8 17 | Number of Lines = 128 18 | Samples per Line = 128 19 | Image Size = 128 x 128 20 | Raw Image Orientation = Landscape 21 | Number of Img components = 3 22 | Component[1]: ID=0x01, Samp Fac=0x22 (Subsamp 1 x 1), Quant Tbl Sel=0x00 (Lum: Y) 23 | Component[2]: ID=0x02, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cb) 24 | Component[3]: ID=0x03, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cr) 25 | 26 | *** Marker: DHT (Define Huffman Table) (xFFC4) *** 27 | OFFSET: 0x00000015 28 | Huffman table length = 51 29 | ---- 30 | Destination ID = 0 31 | Class = 0 (DC / Lossless Table) 32 | Codes of length 01 bits (000 total): 33 | Codes of length 02 bits (002 total): 01 02 34 | Codes of length 03 bits (003 total): 00 03 04 35 | Codes of length 04 bits (001 total): 05 36 | Codes of length 05 bits (001 total): 06 37 | Codes of length 06 bits (001 total): 07 38 | Codes of length 07 bits (000 total): 39 | Codes of length 08 bits (000 total): 40 | Codes of length 09 bits (000 total): 41 | Codes of length 10 bits (000 total): 42 | Codes of length 11 bits (000 total): 43 | Codes of length 12 bits (000 total): 44 | Codes of length 13 bits (000 total): 45 | Codes of length 14 bits (000 total): 46 | Codes of length 15 bits (000 total): 47 | Codes of length 16 bits (000 total): 48 | Total number of codes: 008 49 | 50 | ---- 51 | Destination ID = 1 52 | Class = 0 (DC / Lossless Table) 53 | Codes of length 01 bits (001 total): 00 54 | Codes of length 02 bits (001 total): 01 55 | Codes of length 03 bits (001 total): 02 56 | Codes of length 04 bits (001 total): 03 57 | Codes of length 05 bits (001 total): 04 58 | Codes of length 06 bits (001 total): 05 59 | Codes of length 07 bits (001 total): 06 60 | Codes of length 08 bits (000 total): 61 | Codes of length 09 bits (000 total): 62 | Codes of length 10 bits (000 total): 63 | Codes of length 11 bits (000 total): 64 | Codes of length 12 bits (000 total): 65 | Codes of length 13 bits (000 total): 66 | Codes of length 14 bits (000 total): 67 | Codes of length 15 bits (000 total): 68 | Codes of length 16 bits (000 total): 69 | Total number of codes: 007 70 | 71 | 72 | *** Marker: SOS (Start of Scan) (xFFDA) *** 73 | OFFSET: 0x0000004A 74 | Scan header length = 12 75 | Number of img components = 3 76 | Component[1]: selector=0x01, table=0(DC),0(AC) 77 | Component[2]: selector=0x02, table=1(DC),0(AC) 78 | Component[3]: selector=0x03, table=1(DC),0(AC) 79 | Spectral selection = 6 .. 0 80 | Successive approximation = 0x00 81 | 82 | NOTE: Scan parsing doesn't support this SOF mode. 83 | 84 | *** Marker: EOI (End of Image) (xFFD9) *** 85 | OFFSET: 0x00003213 86 | 87 | Skipping compression signature search as no DQT 88 | -------------------------------------------------------------------------------- /tests/Assets/huffman_lossless/lossless7_s22.jpg.txt: -------------------------------------------------------------------------------- 1 | 2 | JPEGsnoop 1.8.0a by Calvin Hass 3 | http://www.impulseadventure.com/photo/ 4 | ------------------------------------- 5 | 6 | Filename: [C:\Users\jinyi\Documents\Repos\JpegLibrary\tests\Assets\huffman_lossless\lossless7_s22.jpg] 7 | Filesize: [14082] Bytes 8 | 9 | Start Offset: 0x00000000 10 | *** Marker: SOI (xFFD8) *** 11 | OFFSET: 0x00000000 12 | 13 | *** Marker: SOF3 (Lossless Process, Huffman) (xFFC3) *** 14 | OFFSET: 0x00000002 15 | Frame header length = 17 16 | Precision = 8 17 | Number of Lines = 128 18 | Samples per Line = 128 19 | Image Size = 128 x 128 20 | Raw Image Orientation = Landscape 21 | Number of Img components = 3 22 | Component[1]: ID=0x01, Samp Fac=0x22 (Subsamp 1 x 1), Quant Tbl Sel=0x00 (Lum: Y) 23 | Component[2]: ID=0x02, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cb) 24 | Component[3]: ID=0x03, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cr) 25 | 26 | *** Marker: DHT (Define Huffman Table) (xFFC4) *** 27 | OFFSET: 0x00000015 28 | Huffman table length = 51 29 | ---- 30 | Destination ID = 0 31 | Class = 0 (DC / Lossless Table) 32 | Codes of length 01 bits (000 total): 33 | Codes of length 02 bits (002 total): 02 03 34 | Codes of length 03 bits (003 total): 00 01 04 35 | Codes of length 04 bits (001 total): 05 36 | Codes of length 05 bits (001 total): 06 37 | Codes of length 06 bits (001 total): 07 38 | Codes of length 07 bits (000 total): 39 | Codes of length 08 bits (000 total): 40 | Codes of length 09 bits (000 total): 41 | Codes of length 10 bits (000 total): 42 | Codes of length 11 bits (000 total): 43 | Codes of length 12 bits (000 total): 44 | Codes of length 13 bits (000 total): 45 | Codes of length 14 bits (000 total): 46 | Codes of length 15 bits (000 total): 47 | Codes of length 16 bits (000 total): 48 | Total number of codes: 008 49 | 50 | ---- 51 | Destination ID = 1 52 | Class = 0 (DC / Lossless Table) 53 | Codes of length 01 bits (000 total): 54 | Codes of length 02 bits (003 total): 00 01 02 55 | Codes of length 03 bits (001 total): 03 56 | Codes of length 04 bits (001 total): 04 57 | Codes of length 05 bits (001 total): 05 58 | Codes of length 06 bits (001 total): 06 59 | Codes of length 07 bits (000 total): 60 | Codes of length 08 bits (000 total): 61 | Codes of length 09 bits (000 total): 62 | Codes of length 10 bits (000 total): 63 | Codes of length 11 bits (000 total): 64 | Codes of length 12 bits (000 total): 65 | Codes of length 13 bits (000 total): 66 | Codes of length 14 bits (000 total): 67 | Codes of length 15 bits (000 total): 68 | Codes of length 16 bits (000 total): 69 | Total number of codes: 007 70 | 71 | 72 | *** Marker: SOS (Start of Scan) (xFFDA) *** 73 | OFFSET: 0x0000004A 74 | Scan header length = 12 75 | Number of img components = 3 76 | Component[1]: selector=0x01, table=0(DC),0(AC) 77 | Component[2]: selector=0x02, table=1(DC),0(AC) 78 | Component[3]: selector=0x03, table=1(DC),0(AC) 79 | Spectral selection = 7 .. 0 80 | Successive approximation = 0x00 81 | 82 | NOTE: Scan parsing doesn't support this SOF mode. 83 | 84 | *** Marker: EOI (End of Image) (xFFD9) *** 85 | OFFSET: 0x00003700 86 | 87 | Skipping compression signature search as no DQT 88 | -------------------------------------------------------------------------------- /src/JpegLibrary/JpegMathHelper.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Numerics; 5 | using System.Runtime.CompilerServices; 6 | using System.Runtime.InteropServices; 7 | 8 | namespace JpegLibrary 9 | { 10 | internal static class JpegMathHelper 11 | { 12 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 13 | public static int RoundToInt32(float value) 14 | { 15 | #if NO_MATHF 16 | return (int)Math.Round(value); 17 | #else 18 | return (int)MathF.Round(value); 19 | #endif 20 | } 21 | 22 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 23 | public static short RoundToInt16(float value) 24 | { 25 | #if NO_MATHF 26 | return (short)Math.Round(value); 27 | #else 28 | return (short)MathF.Round(value); 29 | #endif 30 | } 31 | 32 | 33 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 34 | public static int Clamp(int value, int min, int max) 35 | { 36 | #if NO_MATH_CLAMP 37 | return Math.Min(Math.Max(value, min), max); 38 | #else 39 | return Math.Clamp(value, min, max); 40 | #endif 41 | } 42 | 43 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 44 | public static float Clamp(float value, float min, float max) 45 | { 46 | #if NO_MATH_CLAMP 47 | return Math.Min(Math.Max(value, min), max); 48 | #else 49 | return Math.Clamp(value, min, max); 50 | #endif 51 | } 52 | 53 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 54 | public static int Log2(uint value) 55 | { 56 | #if NO_BIT_OPERATIONS 57 | return Log2SoftwareFallback(value); 58 | #else 59 | return BitOperations.Log2(value); 60 | #endif 61 | } 62 | 63 | #if NO_BIT_OPERATIONS 64 | private static ReadOnlySpan Log2DeBruijn => new byte[32] 65 | { 66 | 00, 09, 01, 10, 13, 21, 02, 29, 67 | 11, 14, 16, 18, 22, 25, 03, 30, 68 | 08, 12, 20, 28, 15, 17, 24, 07, 69 | 19, 27, 23, 06, 26, 05, 04, 31 70 | }; 71 | 72 | /// 73 | /// Returns the integer (floor) log of the specified value, base 2. 74 | /// Note that by convention, input value 0 returns 0 since Log(0) is undefined. 75 | /// Does not directly use any hardware intrinsics, nor does it incur branching. 76 | /// 77 | /// The value. 78 | private static int Log2SoftwareFallback(uint value) 79 | { 80 | // No AggressiveInlining due to large method size 81 | // Has conventional contract 0->0 (Log(0) is undefined) 82 | 83 | // Fill trailing zeros with ones, eg 00010010 becomes 00011111 84 | value |= value >> 01; 85 | value |= value >> 02; 86 | value |= value >> 04; 87 | value |= value >> 08; 88 | value |= value >> 16; 89 | 90 | // uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check 91 | return Unsafe.AddByteOffset( 92 | // Using deBruijn sequence, k=2, n=5 (2^5=32) : 0b_0000_0111_1100_0100_1010_1100_1101_1101u 93 | ref MemoryMarshal.GetReference(Log2DeBruijn), 94 | // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here 95 | (IntPtr)(int)((value * 0x07C4ACDDu) >> 27)); 96 | } 97 | #endif 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /apps/JpegDecode/DecodeAction.cs: -------------------------------------------------------------------------------- 1 | using JpegLibrary; 2 | using JpegLibrary.ColorConverters; 3 | using SixLabors.ImageSharp; 4 | using SixLabors.ImageSharp.Advanced; 5 | using SixLabors.ImageSharp.PixelFormats; 6 | using System; 7 | using System.Buffers; 8 | using System.IO; 9 | using System.Runtime.InteropServices; 10 | using System.Threading.Tasks; 11 | 12 | namespace JpegDecode 13 | { 14 | public class DecodeAction 15 | { 16 | 17 | public static Task Decode(FileInfo source, string output) 18 | { 19 | using var writer = new MemoryPoolBufferWriter(); 20 | 21 | using (FileStream stream = source.OpenRead()) 22 | { 23 | ReadAllBytes(stream, writer); 24 | } 25 | 26 | var decoder = new JpegDecoder(); 27 | decoder.SetInput(writer.GetReadOnlySequence()); 28 | decoder.Identify(); 29 | 30 | if (decoder.NumberOfComponents != 1 && decoder.NumberOfComponents != 3) 31 | { 32 | // We only support Grayscale and YCbCr. 33 | throw new NotSupportedException("This color space is not supported"); 34 | } 35 | 36 | int width = decoder.Width; 37 | int height = decoder.Height; 38 | 39 | byte[] ycbcr = new byte[width * height * 3]; 40 | 41 | if (decoder.Precision == 8) 42 | { 43 | // This is the most common case for JPEG. 44 | // We use the fatest implement. 45 | decoder.SetOutputWriter(new JpegBufferOutputWriter8Bit(width, height, 3, ycbcr)); 46 | } 47 | else if (decoder.Precision < 8) 48 | { 49 | decoder.SetOutputWriter(new JpegBufferOutputWriterLessThan8Bit(width, height, decoder.Precision, 3, ycbcr)); 50 | } 51 | else 52 | { 53 | decoder.SetOutputWriter(new JpegBufferOutputWriterGreaterThan8Bit(width, height, decoder.Precision, 3, ycbcr)); 54 | } 55 | 56 | decoder.Decode(); 57 | 58 | if (decoder.NumberOfComponents == 1) 59 | { 60 | // For grayscale image, we need to fill Cb and Cr in the YCbCr buffer. 61 | for (int i = 0; i < ycbcr.Length; i += 3) 62 | { 63 | ycbcr[i + 1] = 128; 64 | ycbcr[i + 2] = 128; 65 | } 66 | } 67 | 68 | using var image = new Image(width, height); 69 | 70 | // Convert YCbCr to RGB 71 | for (int i = 0; i < height; i++) 72 | { 73 | JpegYCbCrToRgbConverter.Shared.ConvertYCbCr8ToRgb24(ycbcr.AsSpan(i * width * 3, width * 3), MemoryMarshal.AsBytes(image.GetPixelRowSpan(i)), width); 74 | } 75 | 76 | image.Save(output); 77 | 78 | return Task.FromResult(0); 79 | } 80 | 81 | const int BufferSize = 16384; 82 | 83 | private static void ReadAllBytes(Stream stream, IBufferWriter writer) 84 | { 85 | long length = stream.Length; 86 | while (length > 0) 87 | { 88 | int readSize = (int)Math.Min(length, BufferSize); 89 | Span buffer = writer.GetSpan(readSize); 90 | readSize = stream.Read(buffer); 91 | if (readSize == 0) 92 | { 93 | break; 94 | } 95 | writer.Advance(readSize); 96 | length -= readSize; 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /apps/JpegDecode/JpegBufferOutputWriterLessThan8Bit.cs: -------------------------------------------------------------------------------- 1 | using JpegLibrary; 2 | using System; 3 | using System.Diagnostics; 4 | using System.Runtime.CompilerServices; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace JpegDecode 8 | { 9 | public class JpegBufferOutputWriterLessThan8Bit : JpegBlockOutputWriter 10 | { 11 | private int _width; 12 | private int _height; 13 | private int _precision; 14 | private int _componentCount; 15 | private Memory _output; 16 | 17 | public JpegBufferOutputWriterLessThan8Bit(int width, int height, int precision, int componentCount, Memory output) 18 | { 19 | if (output.Length < (width * height * componentCount)) 20 | { 21 | throw new ArgumentException("Destination buffer is too small."); 22 | } 23 | if (precision > 8) 24 | { 25 | throw new ArgumentOutOfRangeException(nameof(precision)); 26 | } 27 | 28 | _width = width; 29 | _height = height; 30 | _precision = precision; 31 | _componentCount = componentCount; 32 | _output = output; 33 | } 34 | 35 | public override void WriteBlock(ref short blockRef, int componentIndex, int x, int y) 36 | { 37 | int componentCount = _componentCount; 38 | int width = _width; 39 | int height = _height; 40 | int precision = _precision; 41 | int max = (1 << precision) - 1; 42 | 43 | if (x > width || y > _height) 44 | { 45 | return; 46 | } 47 | 48 | int writeWidth = Math.Min(width - x, 8); 49 | int writeHeight = Math.Min(height - y, 8); 50 | 51 | ref byte destinationRef = ref MemoryMarshal.GetReference(_output.Span); 52 | destinationRef = ref Unsafe.Add(ref destinationRef, y * width * componentCount + x * componentCount + componentIndex); 53 | 54 | for (int destY = 0; destY < writeHeight; destY++) 55 | { 56 | ref byte destinationRowRef = ref Unsafe.Add(ref destinationRef, destY * width * componentCount); 57 | for (int destX = 0; destX < writeWidth; destX++) 58 | { 59 | int value = Math.Clamp(Unsafe.Add(ref blockRef, destX), 0, max); 60 | Unsafe.Add(ref destinationRowRef, destX * componentCount) = (byte)ExpandBits((uint)value, precision); 61 | } 62 | blockRef = ref Unsafe.Add(ref blockRef, 8); 63 | } 64 | } 65 | 66 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 67 | private static uint FastExpandBits(uint bits, int bitCount) 68 | { 69 | const int TargetBitCount = 8; 70 | Debug.Assert(bitCount * 2 >= TargetBitCount); 71 | int remainingBits = TargetBitCount - bitCount; 72 | return (bits << remainingBits) | (bits & ((uint)(1 << remainingBits) - 1)); 73 | } 74 | 75 | private static uint ExpandBits(uint bits, int bitCount) 76 | { 77 | const int TargetBitCount = 8; 78 | int currentBitCount = bitCount; 79 | while (currentBitCount < TargetBitCount) 80 | { 81 | bits = (bits << bitCount) | bits; 82 | currentBitCount += bitCount; 83 | } 84 | 85 | if (currentBitCount > TargetBitCount) 86 | { 87 | bits = bits >> bitCount; 88 | currentBitCount -= bitCount; 89 | bits = FastExpandBits(bits, currentBitCount); 90 | } 91 | 92 | return bits; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Tests/Utils/ImageHelper.cs: -------------------------------------------------------------------------------- 1 | using SixLabors.ImageSharp; 2 | using SixLabors.ImageSharp.Advanced; 3 | using SixLabors.ImageSharp.PixelFormats; 4 | using System; 5 | using System.IO; 6 | using System.Runtime.CompilerServices; 7 | 8 | namespace JpegLibrary.Tests 9 | { 10 | internal static class ImageHelper 11 | { 12 | public static ushort[] LoadBuffer(string pathBase, int width, int height, int numberOfComponents) 13 | { 14 | string pathHigh = pathBase + ".high.png"; 15 | string pathLowDiff = pathBase + ".low-diff.png"; 16 | 17 | using var high = Image.Load(pathHigh); 18 | using var lowDiff = Image.Load(pathLowDiff); 19 | 20 | if (high.Width != width || high.Height != height || lowDiff.Width != width || lowDiff.Height != height) 21 | { 22 | throw new InvalidDataException(); 23 | } 24 | 25 | Rgba32[] highPixels = new Rgba32[width * height]; 26 | Rgba32[] lowDiffPixels = new Rgba32[width * height]; 27 | 28 | for (int i = 0; i < height; i++) 29 | { 30 | high.GetPixelRowSpan(i).CopyTo(highPixels.AsSpan(width * i, width)); 31 | lowDiff.GetPixelRowSpan(i).CopyTo(lowDiffPixels.AsSpan(width * i, width)); 32 | } 33 | 34 | ushort[] buffer = new ushort[width * height * 4]; 35 | 36 | CopyHighBits(highPixels, buffer, numberOfComponents); 37 | 38 | CopyLowBits(lowDiffPixels, buffer, numberOfComponents); 39 | 40 | ReversePrediction(buffer, numberOfComponents); 41 | 42 | return buffer; 43 | } 44 | 45 | private static void CopyHighBits(Rgba32[] pixels, ushort[] buffer, int numberOfComponents) 46 | { 47 | ref byte pixelRef = ref Unsafe.As(ref pixels[0]); 48 | ref ushort bufferRef = ref buffer[0]; 49 | 50 | for (int i = 0; i < pixels.Length; i++) 51 | { 52 | for (int n = 0; n < numberOfComponents; n++) 53 | { 54 | Unsafe.Add(ref bufferRef, n) = (ushort)(Unsafe.Add(ref pixelRef, n) << 8); 55 | } 56 | bufferRef = ref Unsafe.Add(ref bufferRef, 4); 57 | pixelRef = ref Unsafe.Add(ref pixelRef, 4); 58 | } 59 | } 60 | 61 | private static void CopyLowBits(Rgba32[] pixels, ushort[] buffer, int numberOfComponents) 62 | { 63 | ref byte pixelRef = ref Unsafe.As(ref pixels[0]); 64 | ref ushort bufferRef = ref buffer[0]; 65 | 66 | for (int i = 0; i < pixels.Length; i++) 67 | { 68 | for (int n = 0; n < numberOfComponents; n++) 69 | { 70 | Unsafe.Add(ref bufferRef, n) = (ushort)(Unsafe.Add(ref bufferRef, n) | Unsafe.Add(ref pixelRef, n)); 71 | } 72 | bufferRef = ref Unsafe.Add(ref bufferRef, 4); 73 | pixelRef = ref Unsafe.Add(ref pixelRef, 4); 74 | } 75 | } 76 | 77 | private static void ReversePrediction(ushort[] buffer, int numberOfComponents) 78 | { 79 | int pixelCount = buffer.Length / 4; 80 | for (int i = 0; i < pixelCount; i++) 81 | { 82 | for (int n = 0; n < numberOfComponents; n++) 83 | { 84 | ref ushort bufferRef = ref buffer[i * 4 + n]; 85 | int high = bufferRef & 0xff00; 86 | int low = (byte)bufferRef; 87 | low = (high >> 8) ^ low; 88 | bufferRef = (ushort)(high | low); 89 | } 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/JpegLibrary/JpegHuffmanEncodingTable.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Diagnostics; 5 | 6 | namespace JpegLibrary 7 | { 8 | /// 9 | /// A Huffman encoding table to encode symbols into JPEG stream. 10 | /// 11 | public class JpegHuffmanEncodingTable 12 | { 13 | private readonly int _codeCount; 14 | private readonly JpegHuffmanCanonicalCode[] _codes; 15 | private readonly byte[] _symbolMap; 16 | 17 | /// 18 | /// Initialize the table with the specified canonical code. 19 | /// 20 | /// The canonical code used to initialize the table. 21 | public JpegHuffmanEncodingTable(JpegHuffmanCanonicalCode[] codes) 22 | { 23 | _codes = codes ?? throw new ArgumentNullException(nameof(codes)); 24 | 25 | int codeCount = 0; 26 | _symbolMap = new byte[256]; 27 | for (int i = 0; i < codes.Length; i++) 28 | { 29 | JpegHuffmanCanonicalCode code = codes[i]; 30 | if (code.CodeLength != 0) 31 | { 32 | _symbolMap[code.Symbol] = (byte)i; 33 | codeCount++; 34 | } 35 | } 36 | _codeCount = codeCount; 37 | } 38 | 39 | /// 40 | /// the count of bytes required to encode this Huffman table. 41 | /// 42 | public ushort BytesRequired => (ushort)(16 + _codeCount); 43 | 44 | /// 45 | /// Write the Huffman table into the buffer specified. 46 | /// 47 | /// The buffer to write to. 48 | /// The count of bytes written. 49 | /// True if the destination buffer is large enough. 50 | public bool TryWrite(Span buffer, out int bytesWritten) 51 | { 52 | bytesWritten = 0; 53 | if (buffer.Length < 16) 54 | { 55 | return false; 56 | } 57 | 58 | for (int len = 1; len <= 16; len++) 59 | { 60 | int count = 0; 61 | for (int i = _codes.Length - _codeCount; i < _codes.Length; i++) 62 | { 63 | if (_codes[i].CodeLength == len) 64 | { 65 | count++; 66 | } 67 | } 68 | buffer[len - 1] = (byte)count; 69 | } 70 | buffer = buffer.Slice(16); 71 | bytesWritten += 16; 72 | 73 | if (buffer.Length < _codeCount) 74 | { 75 | return false; 76 | } 77 | 78 | int index = 0; 79 | for (int i = _codes.Length - _codeCount; i < _codes.Length; i++) 80 | { 81 | buffer[index++] = _codes[i].Symbol; 82 | } 83 | bytesWritten += index; 84 | 85 | return true; 86 | } 87 | 88 | /// 89 | /// Get the Huffman code for the specified symbol. 90 | /// 91 | /// The symbol to encode. 92 | /// The Huffman code of the symbol. 93 | /// The length of the Huffman code. 94 | public void GetCode(int symbol, out ushort code, out int codeLength) 95 | { 96 | Debug.Assert((uint)symbol < 256); 97 | JpegHuffmanCanonicalCode c = _codes[_symbolMap[symbol]]; 98 | code = c.Code; 99 | codeLength = c.CodeLength; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /apps/JpegEncode/JpegRgbToYCbCrConverter.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Runtime.CompilerServices; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace JpegLibrary.ColorConverters 8 | { 9 | public class JpegRgbToYCbCrConverter 10 | { 11 | public static JpegRgbToYCbCrConverter Shared { get; } = new JpegRgbToYCbCrConverter(); 12 | 13 | private int[] _yRTable; 14 | private int[] _yGTable; 15 | private int[] _yBTable; 16 | private int[] _cbRTable; 17 | private int[] _cbGTable; 18 | private int[] _cbBTable; 19 | private int[] _crGTable; 20 | private int[] _crBTable; 21 | 22 | private const int ScaleBits = 16; 23 | private const int CBCrOffset = 128 << ScaleBits; 24 | private const int Half = 1 << (ScaleBits - 1); 25 | 26 | public JpegRgbToYCbCrConverter() 27 | { 28 | _yRTable = new int[256]; 29 | _yGTable = new int[256]; 30 | _yBTable = new int[256]; 31 | _cbRTable = new int[256]; 32 | _cbGTable = new int[256]; 33 | _cbBTable = new int[256]; 34 | _crGTable = new int[256]; 35 | _crBTable = new int[256]; 36 | 37 | for (int i = 0; i < 256; i++) 38 | { 39 | // The values for the calculations are left scaled up since we must add them together before rounding. 40 | _yRTable[i] = Fix(0.299F) * i; 41 | _yGTable[i] = Fix(0.587F) * i; 42 | _yBTable[i] = (Fix(0.114F) * i) + Half; 43 | _cbRTable[i] = (-Fix(0.168735892F)) * i; 44 | _cbGTable[i] = (-Fix(0.331264108F)) * i; 45 | 46 | // We use a rounding fudge - factor of 0.5 - epsilon for Cb and Cr. 47 | // This ensures that the maximum output will round to 255 48 | // not 256, and thus that we don't have to range-limit. 49 | // 50 | // B=>Cb and R=>Cr tables are the same 51 | _cbBTable[i] = (Fix(0.5F) * i) + CBCrOffset + Half - 1; 52 | 53 | _crGTable[i] = (-Fix(0.418687589F)) * i; 54 | _crBTable[i] = (-Fix(0.081312411F)) * i; 55 | } 56 | } 57 | 58 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 59 | private static int Fix(float x) 60 | { 61 | return (int)((x * (1L << ScaleBits)) + 0.5F); 62 | } 63 | 64 | public void ConvertRgb24ToYCbCr8(ReadOnlySpan rgb, Span ycbcr, int count) 65 | { 66 | if (rgb.Length < 3 * count) 67 | { 68 | throw new ArgumentException("RGB buffer is too small.", nameof(rgb)); 69 | } 70 | if (ycbcr.Length < 3 * count) 71 | { 72 | throw new ArgumentException("YCbCr buffer is too small.", nameof(ycbcr)); 73 | } 74 | 75 | ref byte sourceRef = ref MemoryMarshal.GetReference(rgb); 76 | ref byte destinationRef = ref MemoryMarshal.GetReference(ycbcr); 77 | 78 | byte r, g, b; 79 | 80 | for (int i = 0; i < count; i++) 81 | { 82 | r = sourceRef; 83 | g = Unsafe.Add(ref sourceRef, 1); 84 | b = Unsafe.Add(ref sourceRef, 2); 85 | 86 | destinationRef = (byte)((_yRTable[r] + _yGTable[g] + _yBTable[b]) >> ScaleBits); 87 | Unsafe.Add(ref destinationRef, 1) = (byte)((_cbRTable[r] + _cbGTable[g] + _cbBTable[b]) >> ScaleBits); 88 | Unsafe.Add(ref destinationRef, 2) = (byte)((_cbBTable[r] + _crGTable[g] + _crBTable[b]) >> ScaleBits); 89 | 90 | sourceRef = ref Unsafe.Add(ref sourceRef, 3); 91 | destinationRef = ref Unsafe.Add(ref destinationRef, 3); 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/JpegLibrary/JpegStandardQuantizationTable.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | 5 | namespace JpegLibrary 6 | { 7 | /// 8 | /// Helper class for generating standard JPEG quantization table. 9 | /// 10 | public static class JpegStandardQuantizationTable 11 | { 12 | private static readonly ushort[] s_luminanceTable = new ushort[] 13 | { 14 | 16, 11, 12, 14, 12, 10, 16, 14, 15 | 13, 14, 18, 17, 16, 19, 24, 40, 16 | 26, 24, 22, 22, 24, 49, 35, 37, 17 | 29, 40, 58, 51, 61, 60, 57, 51, 18 | 56, 55, 64, 72, 92, 78, 64, 68, 19 | 87, 69, 55, 56, 80, 109, 81, 87, 20 | 95, 98, 103, 104, 103, 62, 77, 113, 21 | 121, 112, 100, 120, 92, 101, 103, 99 22 | }; 23 | 24 | private static readonly ushort[] s_chrominanceTable = new ushort[] 25 | { 26 | 17, 18, 18, 24, 21, 24, 47, 26, 27 | 26, 47, 99, 66, 56, 66, 99, 99, 28 | 99, 99, 99, 99, 99, 99, 99, 99, 29 | 99, 99, 99, 99, 99, 99, 99, 99, 30 | 99, 99, 99, 99, 99, 99, 99, 99, 31 | 99, 99, 99, 99, 99, 99, 99, 99, 32 | 99, 99, 99, 99, 99, 99, 99, 99, 33 | 99, 99, 99, 99, 99, 99, 99, 99 34 | }; 35 | 36 | /// 37 | /// Gets the standard quantization for luminance component. 38 | /// 39 | /// The element precision. 40 | /// The identifier of the table. 41 | /// The standard quantization table. 42 | public static JpegQuantizationTable GetLuminanceTable(JpegElementPrecision elementPrecision, byte identifier) 43 | { 44 | return new JpegQuantizationTable((byte)elementPrecision, identifier, s_luminanceTable); 45 | } 46 | 47 | /// 48 | /// Gets the standard quantization for chrominance component. 49 | /// 50 | /// The element precision. 51 | /// The identifier of the table. 52 | /// The standard quantization table. 53 | public static JpegQuantizationTable GetChrominanceTable(JpegElementPrecision elementPrecision, byte identifier) 54 | { 55 | return new JpegQuantizationTable((byte)elementPrecision, identifier, s_chrominanceTable); 56 | } 57 | 58 | /// 59 | /// Scale the quantization table to match the specified JPEG quality. 60 | /// 61 | /// The standard quantization to scale. 62 | /// The quality factor. 63 | /// The scaled quantization table. 64 | public static JpegQuantizationTable ScaleByQuality(JpegQuantizationTable quantizationTable, int quality) 65 | { 66 | if (quantizationTable.IsEmpty) 67 | { 68 | throw new ArgumentException("Quantization table is not initialized.", nameof(quantizationTable)); 69 | } 70 | if ((uint)quality > 100) 71 | { 72 | throw new ArgumentOutOfRangeException(nameof(quality)); 73 | } 74 | 75 | int scale = quality < 50 ? 5000 / quality : 200 - (quality * 2); 76 | 77 | ReadOnlySpan source = quantizationTable.Elements; 78 | ushort[] elements = new ushort[64]; 79 | for (int i = 0; i < elements.Length; i++) 80 | { 81 | int x = source[i]; 82 | x = ((x * scale) + 50) / 100; 83 | elements[i] = (ushort)JpegMathHelper.Clamp(x, 1, 255); 84 | } 85 | 86 | return new JpegQuantizationTable(quantizationTable.ElementPrecision, quantizationTable.Identifier, elements); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/JpegLibrary/JpegZigZag.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Diagnostics; 5 | using System.Runtime.CompilerServices; 6 | using System.Runtime.InteropServices; 7 | 8 | namespace JpegLibrary 9 | { 10 | /// 11 | /// This class provide methods for converting between natural order and zig-zag order. 12 | /// 13 | public static class JpegZigZag 14 | { 15 | private static ReadOnlySpan s_blockToBuffer => new byte[] 16 | { 17 | 0, 1, 5, 6, 14, 15, 27, 28, 18 | 2, 4, 7, 13, 16, 26, 29, 42, 19 | 3, 8, 12, 17, 25, 30, 41, 43, 20 | 9, 11, 18, 24, 31, 40, 44, 53, 21 | 10, 19, 23, 32, 39, 45, 52, 54, 22 | 20, 22, 33, 38, 46, 51, 55, 60, 23 | 21, 34, 37, 47, 50, 56, 59, 61, 24 | 35, 36, 48, 49, 57, 58, 62, 63 25 | }; 26 | 27 | private static ReadOnlySpan s_bufferToBlock => new byte[] 28 | { 29 | 0, 1, 8, 16, 9, 2, 3, 10, 30 | 17, 24, 32, 25, 18, 11, 4, 5, 31 | 12, 19, 26, 33, 40, 48, 41, 34, 32 | 27, 20, 13, 6, 7, 14, 21, 28, 33 | 35, 42, 49, 56, 57, 50, 43, 36, 34 | 29, 22, 15, 23, 30, 37, 44, 51, 35 | 58, 59, 52, 45, 38, 31, 39, 46, 36 | 53, 60, 61, 54, 47, 55, 62, 63 37 | }; 38 | 39 | /// 40 | /// Convert index of natural order into zig-zag order. 41 | /// 42 | /// Index of natural order starting from zero. 43 | /// Index of zig-zag order. 44 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 45 | public static int InternalBlockIndexToBuffer(int index) 46 | { 47 | Debug.Assert((uint)index < 64); 48 | return Unsafe.Add(ref MemoryMarshal.GetReference(s_blockToBuffer), index); 49 | } 50 | 51 | /// 52 | /// Convert index of zig-zag order into natural order. 53 | /// 54 | /// Index of zig-zag order starting from zero. 55 | /// Index of natural order. 56 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 57 | public static int InternalBufferIndexToBlock(int index) 58 | { 59 | Debug.Assert((uint)index < 64); 60 | return Unsafe.Add(ref MemoryMarshal.GetReference(s_bufferToBlock), index); 61 | } 62 | 63 | /// 64 | /// Convert index of natural order into zig-zag order. 65 | /// 66 | /// Index of natural order starting from zero. 67 | /// Index of zig-zag order. 68 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 69 | public static int BlockIndexToBuffer(int index) 70 | { 71 | if ((uint)index >= s_blockToBuffer.Length) 72 | { 73 | ThrowArgumentOutOfRangeException(nameof(index)); 74 | } 75 | return Unsafe.Add(ref MemoryMarshal.GetReference(s_blockToBuffer), index); 76 | } 77 | 78 | /// 79 | /// Convert index of zig-zag order into natural order. 80 | /// 81 | /// Index of zig-zag order starting from zero. 82 | /// Index of natural order. 83 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 84 | public static int BufferIndexToBlock(int index) 85 | { 86 | if ((uint)index >= s_bufferToBlock.Length) 87 | { 88 | ThrowArgumentOutOfRangeException(nameof(index)); 89 | } 90 | return Unsafe.Add(ref MemoryMarshal.GetReference(s_bufferToBlock), index); 91 | } 92 | 93 | private static void ThrowArgumentOutOfRangeException(string parameterName) 94 | { 95 | throw new ArgumentOutOfRangeException(parameterName); 96 | } 97 | 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /apps/JpegDebugDump/DebugDumpAction.cs: -------------------------------------------------------------------------------- 1 | using JpegLibrary; 2 | using SixLabors.ImageSharp; 3 | using SixLabors.ImageSharp.PixelFormats; 4 | using System; 5 | using System.IO; 6 | using System.Runtime.CompilerServices; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace JpegDebugDump 11 | { 12 | internal class DebugDumpAction 13 | { 14 | public static Task DebugDump(FileInfo source, string output, CancellationToken cancellationToken) 15 | { 16 | if (source is null || source.Length == 0) 17 | { 18 | Console.WriteLine("Input file are not specified."); 19 | return Task.FromResult(1); 20 | } 21 | if (string.IsNullOrEmpty(output)) 22 | { 23 | output = source.FullName; 24 | } 25 | 26 | byte[] input = File.ReadAllBytes(source.FullName); 27 | 28 | var decoder = new JpegDecoder(); 29 | decoder.SetInput(input); 30 | decoder.Identify(); 31 | 32 | int numberOfComponents = decoder.NumberOfComponents; 33 | if (numberOfComponents > 4) 34 | { 35 | throw new NotSupportedException("Number of components greater than 4 is not supported."); 36 | } 37 | 38 | ushort[] buffer = new ushort[decoder.Width * decoder.Height * 4]; 39 | var outputWriter = new JpegExtendingOutputWriter(decoder.Width, decoder.Height, 4, decoder.Precision, buffer); 40 | 41 | decoder.SetOutputWriter(outputWriter); 42 | decoder.Decode(); 43 | 44 | // We use RGBA PNG image to store 4 components. 45 | // Its content may be Grayscale, YCbCr or others. 46 | Rgba32[] pixels = new Rgba32[decoder.Width * decoder.Height]; 47 | Array.Fill(pixels, new Rgba32(255, 255, 255, 255)); 48 | using var image = Image.WrapMemory(pixels.AsMemory(), decoder.Width, decoder.Height); 49 | 50 | // high bits 51 | CopyHighBits(buffer, pixels, numberOfComponents); 52 | image.Save(output + ".high.png"); 53 | 54 | // apply prediction 55 | ApplyPrediction(buffer); 56 | 57 | // low bits 58 | CopyLowBits(buffer, pixels, numberOfComponents); 59 | image.Save(output + ".low-diff.png"); 60 | 61 | return Task.FromResult(0); 62 | } 63 | 64 | private static void CopyHighBits(ushort[] buffer, Rgba32[] pixels, int numberOfComponents) 65 | { 66 | ref ushort bufferRef = ref buffer[0]; 67 | ref byte pixelRef = ref Unsafe.As(ref pixels[0]); 68 | 69 | for (int i = 0; i < pixels.Length; i++) 70 | { 71 | for (int n = 0; n < numberOfComponents; n++) 72 | { 73 | Unsafe.Add(ref pixelRef, n) = (byte)(Unsafe.Add(ref bufferRef, n) >> 8); 74 | } 75 | bufferRef = ref Unsafe.Add(ref bufferRef, 4); 76 | pixelRef = ref Unsafe.Add(ref pixelRef, 4); 77 | } 78 | } 79 | 80 | private static void ApplyPrediction(ushort[] buffer) 81 | { 82 | for (int i = 0; i < buffer.Length; i++) 83 | { 84 | int high = buffer[i] >> 8; 85 | int low = (byte)buffer[i]; 86 | buffer[i] = (ushort)(high ^ low); 87 | } 88 | } 89 | 90 | private static void CopyLowBits(ushort[] buffer, Rgba32[] pixels, int numberOfComponents) 91 | { 92 | ref ushort bufferRef = ref buffer[0]; 93 | ref byte pixelRef = ref Unsafe.As(ref pixels[0]); 94 | 95 | for (int i = 0; i < pixels.Length; i++) 96 | { 97 | for (int n = 0; n < numberOfComponents; n++) 98 | { 99 | Unsafe.Add(ref pixelRef, n) = (byte)Unsafe.Add(ref bufferRef, n); 100 | } 101 | bufferRef = ref Unsafe.Add(ref bufferRef, 4); 102 | pixelRef = ref Unsafe.Add(ref pixelRef, 4); 103 | } 104 | } 105 | 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/JpegLibrary/JpegArithmeticDecodingTable.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Buffers; 5 | using System.Diagnostics.CodeAnalysis; 6 | 7 | namespace JpegLibrary 8 | { 9 | internal class JpegArithmeticDecodingTable 10 | { 11 | public JpegArithmeticDecodingTable(byte tableClass, byte identifier) 12 | { 13 | TableClass = tableClass; 14 | Identifier = identifier; 15 | } 16 | 17 | public byte TableClass { get; } 18 | public byte Identifier { get; } 19 | 20 | public void Configure(byte conditioningTableValue) 21 | { 22 | ConditioningTableValue = conditioningTableValue; 23 | if (TableClass == 0) 24 | { 25 | DcL = conditioningTableValue & 0x0f; 26 | DcU = conditioningTableValue >> 4; 27 | AcKx = 0; 28 | } 29 | else 30 | { 31 | DcL = 0; 32 | DcU = 0; 33 | AcKx = conditioningTableValue; 34 | } 35 | } 36 | 37 | public byte ConditioningTableValue { get; private set; } 38 | 39 | public int DcL { get; private set; } 40 | public int DcU { get; private set; } 41 | public int AcKx { get; private set; } 42 | 43 | public static bool TryParse(ReadOnlySequence buffer, [NotNullWhen(true)] out JpegArithmeticDecodingTable? arithmeticTable, out int bytesConsumed) 44 | { 45 | #if NO_READONLYSEQUENCE_FISTSPAN 46 | ReadOnlySpan firstSpan = buffer.First.Span; 47 | #else 48 | ReadOnlySpan firstSpan = buffer.FirstSpan; 49 | #endif 50 | 51 | if (firstSpan.Length >= 2) 52 | { 53 | return TryParse(firstSpan, out arithmeticTable, out bytesConsumed); 54 | } 55 | 56 | bytesConsumed = 0; 57 | if (firstSpan.IsEmpty) 58 | { 59 | arithmeticTable = null; 60 | return false; 61 | } 62 | 63 | byte tableClassAndIdentifier = firstSpan[0]; 64 | bytesConsumed++; 65 | 66 | return TryParse((byte)(tableClassAndIdentifier >> 4), (byte)(tableClassAndIdentifier & 0xf), buffer.Slice(1), out arithmeticTable, ref bytesConsumed); 67 | 68 | } 69 | 70 | public static bool TryParse(ReadOnlySpan buffer, [NotNullWhen(true)] out JpegArithmeticDecodingTable? arithmeticTable, out int bytesConsumed) 71 | { 72 | bytesConsumed = 0; 73 | if (buffer.IsEmpty) 74 | { 75 | arithmeticTable = null; 76 | return false; 77 | } 78 | 79 | byte tableClassAndIdentifier = buffer[0]; 80 | bytesConsumed++; 81 | 82 | return TryParse((byte)(tableClassAndIdentifier >> 4), (byte)(tableClassAndIdentifier & 0xf), buffer.Slice(1), out arithmeticTable, ref bytesConsumed); 83 | } 84 | 85 | public static bool TryParse(byte tableClass, byte identifier, ReadOnlySequence buffer, [NotNullWhen(true)] out JpegArithmeticDecodingTable? arithmeticTable, ref int bytesConsumed) 86 | { 87 | #if NO_READONLYSEQUENCE_FISTSPAN 88 | ReadOnlySpan data = buffer.First.Span; 89 | #else 90 | ReadOnlySpan data = buffer.FirstSpan; 91 | #endif 92 | 93 | return TryParse(tableClass, identifier, data, out arithmeticTable, ref bytesConsumed); 94 | } 95 | 96 | public static bool TryParse(byte tableClass, byte identifier, ReadOnlySpan buffer, [NotNullWhen(true)] out JpegArithmeticDecodingTable? arithmeticTable, ref int bytesConsumed) 97 | { 98 | if (buffer.IsEmpty) 99 | { 100 | arithmeticTable = null; 101 | return false; 102 | } 103 | 104 | byte conditioningTableValue = buffer[0]; 105 | if (tableClass == 1) 106 | { 107 | if (conditioningTableValue < 1 || conditioningTableValue > 63) 108 | { 109 | arithmeticTable = null; 110 | return false; 111 | } 112 | } 113 | 114 | arithmeticTable = new JpegArithmeticDecodingTable(tableClass, identifier); 115 | arithmeticTable.Configure(conditioningTableValue); 116 | bytesConsumed++; 117 | return true; 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Tests/Utils/JpegExtendingOutputWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace JpegLibrary.Tests 7 | { 8 | public class JpegExtendingOutputWriter : JpegBlockOutputWriter 9 | { 10 | private readonly int _width; 11 | private readonly int _height; 12 | private readonly int _componentCount; 13 | private readonly int _precision; 14 | private readonly Memory _output; 15 | 16 | public JpegExtendingOutputWriter(int width, int height, int componentCount, int precision, Memory output) 17 | { 18 | if (output.Length < (width * height * componentCount)) 19 | { 20 | throw new ArgumentException("Destination buffer is too small."); 21 | } 22 | 23 | _width = width; 24 | _height = height; 25 | _componentCount = componentCount; 26 | _precision = precision; 27 | _output = output; 28 | } 29 | 30 | public override void WriteBlock(ref short blockRef, int componentIndex, int x, int y) 31 | { 32 | int precision = _precision; 33 | ushort max = (ushort)((1 << precision) - 1); 34 | int componentCount = _componentCount; 35 | int width = _width; 36 | int height = _height; 37 | 38 | if (x > width || y > _height) 39 | { 40 | return; 41 | } 42 | 43 | int writeWidth = Math.Min(width - x, 8); 44 | int writeHeight = Math.Min(height - y, 8); 45 | 46 | ref ushort destinationRef = ref MemoryMarshal.GetReference(_output.Span); 47 | destinationRef = ref Unsafe.Add(ref destinationRef, y * width * componentCount + x * componentCount + componentIndex); 48 | 49 | // Fast path 50 | if (precision >= 8) 51 | { 52 | for (int destY = 0; destY < writeHeight; destY++) 53 | { 54 | ref ushort destinationRowRef = ref Unsafe.Add(ref destinationRef, destY * width * componentCount); 55 | for (int destX = 0; destX < writeWidth; destX++) 56 | { 57 | uint value = Clamp((ushort)Unsafe.Add(ref blockRef, destX), max); 58 | Unsafe.Add(ref destinationRowRef, destX * componentCount) = (ushort)FastExpandBits(value, precision); 59 | } 60 | blockRef = ref Unsafe.Add(ref blockRef, 8); 61 | } 62 | return; 63 | } 64 | 65 | for (int destY = 0; destY < writeHeight; destY++) 66 | { 67 | ref ushort destinationRowRef = ref Unsafe.Add(ref destinationRef, destY * width * componentCount); 68 | for (int destX = 0; destX < writeWidth; destX++) 69 | { 70 | uint value = Clamp((ushort)Unsafe.Add(ref blockRef, destX), max); 71 | Unsafe.Add(ref destinationRowRef, destX * componentCount) = (ushort)ExpandBits(value, precision); 72 | } 73 | blockRef = ref Unsafe.Add(ref blockRef, 8); 74 | } 75 | } 76 | 77 | private ushort Clamp(ushort value, ushort max) 78 | { 79 | return Math.Clamp(value, (ushort)0, max); 80 | } 81 | 82 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 83 | private static uint FastExpandBits(uint bits, int bitCount) 84 | { 85 | const int TargetBitCount = 16; 86 | Debug.Assert(bitCount * 2 >= TargetBitCount); 87 | int remainingBits = TargetBitCount - bitCount; 88 | return (bits << remainingBits) | (bits & ((uint)(1 << remainingBits) - 1)); 89 | } 90 | 91 | private static uint ExpandBits(uint bits, int bitCount) 92 | { 93 | const int TargetBitCount = 16; 94 | 95 | int currentBitCount = bitCount; 96 | while (currentBitCount < TargetBitCount) 97 | { 98 | bits = (bits << bitCount) | bits; 99 | currentBitCount += bitCount; 100 | } 101 | 102 | if (currentBitCount > TargetBitCount) 103 | { 104 | bits = bits >> bitCount; 105 | currentBitCount -= bitCount; 106 | bits = FastExpandBits(bits, currentBitCount); 107 | } 108 | 109 | return bits; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /apps/JpegDebugDump/JpegExtendingOutputWriter.cs: -------------------------------------------------------------------------------- 1 | using JpegLibrary; 2 | using System; 3 | using System.Diagnostics; 4 | using System.Runtime.CompilerServices; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace JpegDebugDump 8 | { 9 | public class JpegExtendingOutputWriter : JpegBlockOutputWriter 10 | { 11 | private readonly int _width; 12 | private readonly int _height; 13 | private readonly int _componentCount; 14 | private readonly int _precision; 15 | private readonly Memory _output; 16 | 17 | public JpegExtendingOutputWriter(int width, int height, int componentCount, int precision, Memory output) 18 | { 19 | if (output.Length < (width * height * componentCount)) 20 | { 21 | throw new ArgumentException("Destination buffer is too small."); 22 | } 23 | 24 | _width = width; 25 | _height = height; 26 | _componentCount = componentCount; 27 | _precision = precision; 28 | _output = output; 29 | } 30 | 31 | public override void WriteBlock(ref short blockRef, int componentIndex, int x, int y) 32 | { 33 | int precision = _precision; 34 | ushort max = (ushort)((1 << precision) - 1); 35 | int componentCount = _componentCount; 36 | int width = _width; 37 | int height = _height; 38 | 39 | if (x > width || y > _height) 40 | { 41 | return; 42 | } 43 | 44 | int writeWidth = Math.Min(width - x, 8); 45 | int writeHeight = Math.Min(height - y, 8); 46 | 47 | ref ushort destinationRef = ref MemoryMarshal.GetReference(_output.Span); 48 | destinationRef = ref Unsafe.Add(ref destinationRef, y * width * componentCount + x * componentCount + componentIndex); 49 | 50 | // Fast path 51 | if (precision >= 8) 52 | { 53 | for (int destY = 0; destY < writeHeight; destY++) 54 | { 55 | ref ushort destinationRowRef = ref Unsafe.Add(ref destinationRef, destY * width * componentCount); 56 | for (int destX = 0; destX < writeWidth; destX++) 57 | { 58 | uint value = Clamp((ushort)Unsafe.Add(ref blockRef, destX), max); 59 | Unsafe.Add(ref destinationRowRef, destX * componentCount) = (ushort)FastExpandBits(value, precision); 60 | } 61 | blockRef = ref Unsafe.Add(ref blockRef, 8); 62 | } 63 | return; 64 | } 65 | 66 | for (int destY = 0; destY < writeHeight; destY++) 67 | { 68 | ref ushort destinationRowRef = ref Unsafe.Add(ref destinationRef, destY * width * componentCount); 69 | for (int destX = 0; destX < writeWidth; destX++) 70 | { 71 | uint value = Clamp((ushort)Unsafe.Add(ref blockRef, destX), max); 72 | Unsafe.Add(ref destinationRowRef, destX * componentCount) = (ushort)ExpandBits(value, precision); 73 | } 74 | blockRef = ref Unsafe.Add(ref blockRef, 8); 75 | } 76 | } 77 | 78 | private ushort Clamp(ushort value, ushort max) 79 | { 80 | return Math.Clamp(value, (ushort)0, max); 81 | } 82 | 83 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 84 | private static uint FastExpandBits(uint bits, int bitCount) 85 | { 86 | const int TargetBitCount = 16; 87 | Debug.Assert(bitCount * 2 >= TargetBitCount); 88 | int remainingBits = TargetBitCount - bitCount; 89 | return (bits << remainingBits) | (bits & ((uint)(1 << remainingBits) - 1)); 90 | } 91 | 92 | private static uint ExpandBits(uint bits, int bitCount) 93 | { 94 | const int TargetBitCount = 16; 95 | 96 | int currentBitCount = bitCount; 97 | while (currentBitCount < TargetBitCount) 98 | { 99 | bits = (bits << bitCount) | bits; 100 | currentBitCount += bitCount; 101 | } 102 | 103 | if (currentBitCount > TargetBitCount) 104 | { 105 | bits = bits >> bitCount; 106 | currentBitCount -= bitCount; 107 | bits = FastExpandBits(bits, currentBitCount); 108 | } 109 | 110 | return bits; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Benchmarks/ColorConverters/JpegRgbToYCbCrConverter.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Runtime.CompilerServices; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace JpegLibrary.ColorConverters 8 | { 9 | public class JpegRgbToYCbCrConverter 10 | { 11 | public static JpegRgbToYCbCrConverter Shared { get; } = new JpegRgbToYCbCrConverter(); 12 | 13 | private int[] _yRTable; 14 | private int[] _yGTable; 15 | private int[] _yBTable; 16 | private int[] _cbRTable; 17 | private int[] _cbGTable; 18 | private int[] _cbBTable; 19 | private int[] _crGTable; 20 | private int[] _crBTable; 21 | 22 | private const int ScaleBits = 16; 23 | private const int CBCrOffset = 128 << ScaleBits; 24 | private const int Half = 1 << (ScaleBits - 1); 25 | 26 | public JpegRgbToYCbCrConverter() 27 | { 28 | _yRTable = new int[256]; 29 | _yGTable = new int[256]; 30 | _yBTable = new int[256]; 31 | _cbRTable = new int[256]; 32 | _cbGTable = new int[256]; 33 | _cbBTable = new int[256]; 34 | _crGTable = new int[256]; 35 | _crBTable = new int[256]; 36 | 37 | for (int i = 0; i < 256; i++) 38 | { 39 | // The values for the calculations are left scaled up since we must add them together before rounding. 40 | _yRTable[i] = Fix(0.299F) * i; 41 | _yGTable[i] = Fix(0.587F) * i; 42 | _yBTable[i] = (Fix(0.114F) * i) + Half; 43 | _cbRTable[i] = (-Fix(0.168735892F)) * i; 44 | _cbGTable[i] = (-Fix(0.331264108F)) * i; 45 | 46 | // We use a rounding fudge - factor of 0.5 - epsilon for Cb and Cr. 47 | // This ensures that the maximum output will round to 255 48 | // not 256, and thus that we don't have to range-limit. 49 | // 50 | // B=>Cb and R=>Cr tables are the same 51 | _cbBTable[i] = (Fix(0.5F) * i) + CBCrOffset + Half - 1; 52 | 53 | _crGTable[i] = (-Fix(0.418687589F)) * i; 54 | _crBTable[i] = (-Fix(0.081312411F)) * i; 55 | } 56 | } 57 | 58 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 59 | private static int Fix(float x) 60 | { 61 | return (int)((x * (1L << ScaleBits)) + 0.5F); 62 | } 63 | 64 | public void ConvertRgb24ToYCbCr8(ReadOnlySpan rgb, Span ycbcr, int count) 65 | { 66 | if (rgb.Length < 3 * count) 67 | { 68 | throw new ArgumentException("RGB buffer is too small.", nameof(rgb)); 69 | } 70 | if (ycbcr.Length < 3 * count) 71 | { 72 | throw new ArgumentException("YCbCr buffer is too small.", nameof(ycbcr)); 73 | } 74 | 75 | ref byte sourceRef = ref MemoryMarshal.GetReference(rgb); 76 | ref byte destinationRef = ref MemoryMarshal.GetReference(ycbcr); 77 | 78 | byte r, g, b; 79 | 80 | for (int i = 0; i < count; i++) 81 | { 82 | r = sourceRef; 83 | g = Unsafe.Add(ref sourceRef, 1); 84 | b = Unsafe.Add(ref sourceRef, 2); 85 | 86 | destinationRef = (byte)((_yRTable[r] + _yGTable[g] + _yBTable[b]) >> ScaleBits); 87 | Unsafe.Add(ref destinationRef, 1) = (byte)((_cbRTable[r] + _cbGTable[g] + _cbBTable[b]) >> ScaleBits); 88 | Unsafe.Add(ref destinationRef, 2) = (byte)((_cbBTable[r] + _crGTable[g] + _crBTable[b]) >> ScaleBits); 89 | 90 | sourceRef = ref Unsafe.Add(ref sourceRef, 3); 91 | destinationRef = ref Unsafe.Add(ref destinationRef, 3); 92 | } 93 | } 94 | 95 | public void ConvertRgba32ToYCbCr8(ReadOnlySpan rgba, Span ycbcr, int count) 96 | { 97 | if (rgba.Length < 4 * count) 98 | { 99 | throw new ArgumentException("RGBA buffer is too small.", nameof(rgba)); 100 | } 101 | if (ycbcr.Length < 3 * count) 102 | { 103 | throw new ArgumentException("YCbCr buffer is too small.", nameof(ycbcr)); 104 | } 105 | 106 | ref byte sourceRef = ref MemoryMarshal.GetReference(rgba); 107 | ref byte destinationRef = ref MemoryMarshal.GetReference(ycbcr); 108 | 109 | byte r, g, b; 110 | 111 | for (int i = 0; i < count; i++) 112 | { 113 | r = sourceRef; 114 | g = Unsafe.Add(ref sourceRef, 1); 115 | b = Unsafe.Add(ref sourceRef, 2); 116 | 117 | destinationRef = (byte)((_yRTable[r] + _yGTable[g] + _yBTable[b]) >> ScaleBits); 118 | Unsafe.Add(ref destinationRef, 1) = (byte)((_cbRTable[r] + _cbGTable[g] + _cbBTable[b]) >> ScaleBits); 119 | Unsafe.Add(ref destinationRef, 2) = (byte)((_cbBTable[r] + _crGTable[g] + _crBTable[b]) >> ScaleBits); 120 | 121 | sourceRef = ref Unsafe.Add(ref sourceRef, 4); 122 | destinationRef = ref Unsafe.Add(ref destinationRef, 3); 123 | } 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Benchmarks/JpegRgbToYCbCrComponentConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace JpegLibrary.Benchmarks 6 | { 7 | internal class JpegRgbToYCbCrComponentConverter 8 | { 9 | public static JpegRgbToYCbCrComponentConverter Shared { get; } = new JpegRgbToYCbCrComponentConverter(); 10 | 11 | private int[] _yRTable; 12 | private int[] _yGTable; 13 | private int[] _yBTable; 14 | private int[] _cbRTable; 15 | private int[] _cbGTable; 16 | private int[] _cbBTable; 17 | private int[] _crGTable; 18 | private int[] _crBTable; 19 | 20 | private const int ScaleBits = 16; 21 | private const int CBCrOffset = 128 << ScaleBits; 22 | private const int Half = 1 << (ScaleBits - 1); 23 | 24 | public JpegRgbToYCbCrComponentConverter() 25 | { 26 | _yRTable = new int[256]; 27 | _yGTable = new int[256]; 28 | _yBTable = new int[256]; 29 | _cbRTable = new int[256]; 30 | _cbGTable = new int[256]; 31 | _cbBTable = new int[256]; 32 | _crGTable = new int[256]; 33 | _crBTable = new int[256]; 34 | 35 | for (int i = 0; i < 256; i++) 36 | { 37 | // The values for the calculations are left scaled up since we must add them together before rounding. 38 | _yRTable[i] = Fix(0.299F) * i; 39 | _yGTable[i] = Fix(0.587F) * i; 40 | _yBTable[i] = (Fix(0.114F) * i) + Half; 41 | _cbRTable[i] = (-Fix(0.168735892F)) * i; 42 | _cbGTable[i] = (-Fix(0.331264108F)) * i; 43 | 44 | // We use a rounding fudge - factor of 0.5 - epsilon for Cb and Cr. 45 | // This ensures that the maximum output will round to 255 46 | // not 256, and thus that we don't have to range-limit. 47 | // 48 | // B=>Cb and R=>Cr tables are the same 49 | _cbBTable[i] = (Fix(0.5F) * i) + CBCrOffset + Half - 1; 50 | 51 | _crGTable[i] = (-Fix(0.418687589F)) * i; 52 | _crBTable[i] = (-Fix(0.081312411F)) * i; 53 | } 54 | } 55 | 56 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 57 | private static int Fix(float x) 58 | { 59 | return (int)((x * (1L << ScaleBits)) + 0.5F); 60 | } 61 | 62 | public void ConvertRgba32ToYComponent(ReadOnlySpan rgba, ref short y, int count) 63 | { 64 | if (rgba.Length < 4 * count) 65 | { 66 | throw new ArgumentException("RGB buffer is too small.", nameof(rgba)); 67 | } 68 | 69 | int[] yRTable = _yRTable; 70 | int[] yGTable = _yGTable; 71 | int[] yBTable = _yBTable; 72 | 73 | ref byte sourceRef = ref MemoryMarshal.GetReference(rgba); 74 | 75 | byte r, g, b; 76 | 77 | for (int i = 0; i < count; i++) 78 | { 79 | r = sourceRef; 80 | g = Unsafe.Add(ref sourceRef, 1); 81 | b = Unsafe.Add(ref sourceRef, 2); 82 | 83 | Unsafe.Add(ref y, i) = (short)((yRTable[r] + yGTable[g] + yBTable[b]) >> ScaleBits); 84 | 85 | sourceRef = ref Unsafe.Add(ref sourceRef, 4); 86 | } 87 | } 88 | 89 | public void ConvertRgba32ToCbComponent(ReadOnlySpan rgba, ref short cb, int count) 90 | { 91 | if (rgba.Length < 4 * count) 92 | { 93 | throw new ArgumentException("RGB buffer is too small.", nameof(rgba)); 94 | } 95 | 96 | int[] cbRTable = _cbRTable; 97 | int[] cbGTable = _cbGTable; 98 | int[] cbBTable =_cbBTable; 99 | 100 | ref byte sourceRef = ref MemoryMarshal.GetReference(rgba); 101 | 102 | byte r, g, b; 103 | 104 | for (int i = 0; i < count; i++) 105 | { 106 | r = sourceRef; 107 | g = Unsafe.Add(ref sourceRef, 1); 108 | b = Unsafe.Add(ref sourceRef, 2); 109 | 110 | Unsafe.Add(ref cb, i) = (short)((cbRTable[r] + cbGTable[g] + cbBTable[b]) >> ScaleBits); 111 | 112 | sourceRef = ref Unsafe.Add(ref sourceRef, 4); 113 | } 114 | } 115 | 116 | public void ConvertRgba32ToCrComponent(ReadOnlySpan rgba, ref short cr, int count) 117 | { 118 | if (rgba.Length < 4 * count) 119 | { 120 | throw new ArgumentException("RGB buffer is too small.", nameof(rgba)); 121 | } 122 | 123 | int[] cbBTable = _cbBTable; 124 | int[] crGTable = _crGTable; 125 | int[] crBTable = _crBTable; 126 | 127 | ref byte sourceRef = ref MemoryMarshal.GetReference(rgba); 128 | 129 | byte r, g, b; 130 | 131 | for (int i = 0; i < count; i++) 132 | { 133 | r = sourceRef; 134 | g = Unsafe.Add(ref sourceRef, 1); 135 | b = Unsafe.Add(ref sourceRef, 2); 136 | 137 | Unsafe.Add(ref cr, i) = (short)((cbBTable[r] + crGTable[g] + crBTable[b]) >> ScaleBits); 138 | 139 | sourceRef = ref Unsafe.Add(ref sourceRef, 4); 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/JpegLibrary/ScanDecoder/JpegHuffmanScanDecoder.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Diagnostics; 5 | 6 | namespace JpegLibrary.ScanDecoder 7 | { 8 | internal abstract class JpegHuffmanScanDecoder : JpegScanDecoder 9 | { 10 | protected JpegDecoder Decoder { get; private set; } 11 | 12 | public JpegHuffmanScanDecoder(JpegDecoder decoder) 13 | { 14 | Decoder = decoder; 15 | } 16 | 17 | protected int InitDecodeComponents(JpegFrameHeader frameHeader, JpegScanHeader scanHeader, Span components) 18 | { 19 | Debug.Assert(frameHeader.Components is not null); 20 | Debug.Assert(scanHeader.Components is not null); 21 | 22 | // Compute maximum sampling factor 23 | int maxHorizontalSampling = 1; 24 | int maxVerticalSampling = 1; 25 | foreach (JpegFrameComponentSpecificationParameters currentFrameComponent in frameHeader.Components!) 26 | { 27 | maxHorizontalSampling = Math.Max(maxHorizontalSampling, currentFrameComponent.HorizontalSamplingFactor); 28 | maxVerticalSampling = Math.Max(maxVerticalSampling, currentFrameComponent.VerticalSamplingFactor); 29 | } 30 | 31 | // Resolve each component 32 | if (components.Length < scanHeader.NumberOfComponents) 33 | { 34 | throw new InvalidOperationException(); 35 | } 36 | for (int i = 0; i < scanHeader.NumberOfComponents; i++) 37 | { 38 | JpegScanComponentSpecificationParameters scanComponenet = scanHeader.Components![i]; 39 | int componentIndex = 0; 40 | JpegFrameComponentSpecificationParameters? frameComponent = null; 41 | 42 | for (int j = 0; j < frameHeader.NumberOfComponents; j++) 43 | { 44 | JpegFrameComponentSpecificationParameters currentFrameComponent = frameHeader.Components[j]; 45 | if (scanComponenet.ScanComponentSelector == currentFrameComponent.Identifier) 46 | { 47 | componentIndex = j; 48 | frameComponent = currentFrameComponent; 49 | } 50 | } 51 | if (frameComponent is null) 52 | { 53 | ThrowInvalidDataException("The specified component is missing."); 54 | } 55 | JpegHuffmanDecodingComponent component = components[i]; 56 | if (component is null) 57 | { 58 | components[i] = component = new JpegHuffmanDecodingComponent(); 59 | } 60 | component.ComponentIndex = componentIndex; 61 | component.HorizontalSamplingFactor = frameComponent.GetValueOrDefault().HorizontalSamplingFactor; 62 | component.VerticalSamplingFactor = frameComponent.GetValueOrDefault().VerticalSamplingFactor; 63 | component.DcTable = Decoder.GetHuffmanTable(true, scanComponenet.DcEntropyCodingTableSelector); 64 | component.AcTable = Decoder.GetHuffmanTable(false, scanComponenet.AcEntropyCodingTableSelector); 65 | component.QuantizationTable = Decoder.GetQuantizationTable(frameComponent.GetValueOrDefault().QuantizationTableSelector); 66 | component.HorizontalSubsamplingFactor = maxHorizontalSampling / component.HorizontalSamplingFactor; 67 | component.VerticalSubsamplingFactor = maxVerticalSampling / component.VerticalSamplingFactor; 68 | component.DcPredictor = 0; 69 | } 70 | 71 | return scanHeader.NumberOfComponents; 72 | } 73 | 74 | protected JpegHuffmanDecodingComponent[] InitDecodeComponents(JpegFrameHeader frameHeader, JpegScanHeader scanHeader) 75 | { 76 | JpegHuffmanDecodingComponent[] components = new JpegHuffmanDecodingComponent[scanHeader.NumberOfComponents]; 77 | InitDecodeComponents(frameHeader, scanHeader, components); 78 | return components; 79 | } 80 | 81 | protected static int DecodeHuffmanCode(ref JpegBitReader reader, JpegHuffmanDecodingTable table) 82 | { 83 | int bits = reader.PeekBits(16, out int bitsRead); 84 | JpegHuffmanDecodingTable.Entry entry = table.Lookup(bits); 85 | bitsRead = Math.Min(entry.CodeSize, bitsRead); 86 | _ = reader.TryAdvanceBits(bitsRead, out _); 87 | return entry.SymbolValue; 88 | } 89 | 90 | protected static JpegHuffmanDecodingTable.Entry DecodeHuffmanCode(ref JpegBitReader reader, JpegHuffmanDecodingTable table, out int code, out int bitsRead) 91 | { 92 | int bits = reader.PeekBits(16, out bitsRead); 93 | JpegHuffmanDecodingTable.Entry entry = table.Lookup(bits); 94 | bitsRead = Math.Min(entry.CodeSize, bitsRead); 95 | _ = reader.TryAdvanceBits(bitsRead, out _); 96 | code = bits >> (16 - bitsRead); 97 | return entry; 98 | } 99 | 100 | protected static int ReceiveAndExtend(ref JpegBitReader reader, int length) 101 | { 102 | Debug.Assert(length > 0); 103 | if (!reader.TryReadBits(length, out int value, out bool isMarkerEncountered)) 104 | { 105 | if (isMarkerEncountered) 106 | { 107 | ThrowInvalidDataException("Expect raw data from bit stream. Yet a marker is encountered."); 108 | } 109 | ThrowInvalidDataException("The bit stream ended prematurely."); 110 | } 111 | 112 | return Extend(value, length); 113 | 114 | static int Extend(int v, int nbits) => v - ((((v + v) >> nbits) - 1) & ((1 << nbits) - 1)); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /apps/JpegDecode/MemoryPoolBufferWriter.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Buffers; 5 | using System.Diagnostics; 6 | 7 | namespace System.Buffers 8 | { 9 | internal sealed class MemoryPoolBufferWriter : IBufferWriter, IDisposable 10 | { 11 | private sealed class BufferSegment : ReadOnlySequenceSegment 12 | { 13 | private IMemoryOwner? _memoryOwner; 14 | private Memory _memory; 15 | private BufferSegment? _next; 16 | private int _consumed; 17 | 18 | public int Length => _consumed; 19 | 20 | public int AvailableLength => _memory.Length - _consumed; 21 | 22 | public Memory AvailableMemory => _memory.Slice(_consumed); 23 | 24 | public Span AvailableSpan => _memory.Span.Slice(_consumed); 25 | 26 | public BufferSegment? NextSegment => _next; 27 | 28 | public BufferSegment(long runningIndex, IMemoryOwner memoryOwner) 29 | { 30 | _memoryOwner = memoryOwner; 31 | _memory = memoryOwner.Memory; 32 | _consumed = 0; 33 | 34 | Memory = default; 35 | RunningIndex = runningIndex; 36 | } 37 | 38 | public void Advance(int count) 39 | { 40 | Debug.Assert(count <= AvailableLength); 41 | 42 | _consumed += count; 43 | Memory = _memory.Slice(0, _consumed); 44 | } 45 | 46 | public void SetNext(BufferSegment segment) 47 | { 48 | _next = segment; 49 | 50 | Next = segment; 51 | } 52 | 53 | public void CopyTo(Span destination) 54 | { 55 | _memory.Span.Slice(0, _consumed).CopyTo(destination); 56 | } 57 | 58 | public BufferSegment? ReturnMemory() 59 | { 60 | BufferSegment? next = _next; 61 | 62 | _memoryOwner?.Dispose(); 63 | _memoryOwner = null; 64 | _memory = default; 65 | _next = null; 66 | 67 | Memory = default; 68 | Next = default; 69 | 70 | return next; 71 | } 72 | } 73 | 74 | private MemoryPool _memoryPool; 75 | private BufferSegment? _head; 76 | private BufferSegment? _current; 77 | private int _length; 78 | 79 | public int Length => _length; 80 | 81 | public MemoryPoolBufferWriter(MemoryPool? memoryPool = null) 82 | { 83 | _memoryPool = memoryPool ?? MemoryPool.Shared; 84 | } 85 | 86 | private BufferSegment GetBufferSegment(int sizeHint) 87 | { 88 | BufferSegment? current = _current; 89 | if (current is null) 90 | { 91 | _head = _current = current = new BufferSegment(0, _memoryPool.Rent(Math.Max(sizeHint, 16384))); 92 | } 93 | if (sizeHint < current.AvailableLength) 94 | { 95 | return current; 96 | } 97 | 98 | Debug.Assert(_current != null); 99 | current = new BufferSegment(_length, _memoryPool.Rent(Math.Max(sizeHint, 16384))); 100 | _current!.SetNext(current); 101 | _current = current; 102 | 103 | return current; 104 | } 105 | 106 | public Memory GetMemory(int sizeHint = 0) 107 | { 108 | return GetBufferSegment(sizeHint).AvailableMemory; 109 | } 110 | 111 | public Span GetSpan(int sizeHint = 0) 112 | { 113 | return GetBufferSegment(sizeHint).AvailableSpan; 114 | } 115 | 116 | public void Advance(int count) 117 | { 118 | BufferSegment? current = _current; 119 | if (current is null) 120 | { 121 | throw new InvalidOperationException(); 122 | } 123 | if (count > current.AvailableLength) 124 | { 125 | throw new ArgumentOutOfRangeException(nameof(count)); 126 | } 127 | 128 | current.Advance(count); 129 | _length += count; 130 | } 131 | 132 | public byte[] ToArray() 133 | { 134 | int totalLength = 0; 135 | BufferSegment? segment = _head; 136 | while (segment != null) 137 | { 138 | totalLength += segment.Length; 139 | segment = segment.NextSegment; 140 | } 141 | 142 | byte[] destination = new byte[totalLength]; 143 | int offset = 0; 144 | segment = _head; 145 | while (segment != null) 146 | { 147 | segment.CopyTo(destination.AsSpan(offset)); 148 | offset += segment.Length; 149 | segment = segment.NextSegment; 150 | } 151 | 152 | return destination; 153 | } 154 | 155 | public void CopyTo(Span destination) 156 | { 157 | BufferSegment? segment = _head; 158 | while (segment != null) 159 | { 160 | segment.CopyTo(destination); 161 | destination = destination.Slice(segment.Length); 162 | segment = segment.NextSegment; 163 | } 164 | } 165 | 166 | public ReadOnlySequence GetReadOnlySequence() 167 | { 168 | if (_head is null || _current is null) 169 | { 170 | return ReadOnlySequence.Empty; 171 | } 172 | 173 | return new ReadOnlySequence(_head, 0, _current, _current.Length); 174 | } 175 | 176 | public void Dispose() 177 | { 178 | BufferSegment? segment = _head; 179 | while (segment != null) 180 | { 181 | segment = segment.ReturnMemory(); 182 | } 183 | _head = _current = null; 184 | _length = 0; 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /apps/JpegOptimize/MemoryPoolBufferWriter.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Buffers; 5 | using System.Diagnostics; 6 | 7 | namespace System.Buffers 8 | { 9 | internal sealed class MemoryPoolBufferWriter : IBufferWriter, IDisposable 10 | { 11 | private sealed class BufferSegment : ReadOnlySequenceSegment 12 | { 13 | private IMemoryOwner? _memoryOwner; 14 | private Memory _memory; 15 | private BufferSegment? _next; 16 | private int _consumed; 17 | 18 | public int Length => _consumed; 19 | 20 | public int AvailableLength => _memory.Length - _consumed; 21 | 22 | public Memory AvailableMemory => _memory.Slice(_consumed); 23 | 24 | public Span AvailableSpan => _memory.Span.Slice(_consumed); 25 | 26 | public BufferSegment? NextSegment => _next; 27 | 28 | public BufferSegment(long runningIndex, IMemoryOwner memoryOwner) 29 | { 30 | _memoryOwner = memoryOwner; 31 | _memory = memoryOwner.Memory; 32 | _consumed = 0; 33 | 34 | Memory = default; 35 | RunningIndex = runningIndex; 36 | } 37 | 38 | public void Advance(int count) 39 | { 40 | Debug.Assert(count <= AvailableLength); 41 | 42 | _consumed += count; 43 | Memory = _memory.Slice(0, _consumed); 44 | } 45 | 46 | public void SetNext(BufferSegment segment) 47 | { 48 | _next = segment; 49 | 50 | Next = segment; 51 | } 52 | 53 | public void CopyTo(Span destination) 54 | { 55 | _memory.Span.Slice(0, _consumed).CopyTo(destination); 56 | } 57 | 58 | public BufferSegment? ReturnMemory() 59 | { 60 | BufferSegment? next = _next; 61 | 62 | _memoryOwner?.Dispose(); 63 | _memoryOwner = null; 64 | _memory = default; 65 | _next = null; 66 | 67 | Memory = default; 68 | Next = default; 69 | 70 | return next; 71 | } 72 | } 73 | 74 | private MemoryPool _memoryPool; 75 | private BufferSegment? _head; 76 | private BufferSegment? _current; 77 | private int _length; 78 | 79 | public int Length => _length; 80 | 81 | public MemoryPoolBufferWriter(MemoryPool? memoryPool = null) 82 | { 83 | _memoryPool = memoryPool ?? MemoryPool.Shared; 84 | } 85 | 86 | private BufferSegment GetBufferSegment(int sizeHint) 87 | { 88 | BufferSegment? current = _current; 89 | if (current is null) 90 | { 91 | _head = _current = current = new BufferSegment(0, _memoryPool.Rent(Math.Max(sizeHint, 16384))); 92 | } 93 | if (sizeHint < current.AvailableLength) 94 | { 95 | return current; 96 | } 97 | 98 | Debug.Assert(_current != null); 99 | current = new BufferSegment(_length, _memoryPool.Rent(Math.Max(sizeHint, 16384))); 100 | _current!.SetNext(current); 101 | _current = current; 102 | 103 | return current; 104 | } 105 | 106 | public Memory GetMemory(int sizeHint = 0) 107 | { 108 | return GetBufferSegment(sizeHint).AvailableMemory; 109 | } 110 | 111 | public Span GetSpan(int sizeHint = 0) 112 | { 113 | return GetBufferSegment(sizeHint).AvailableSpan; 114 | } 115 | 116 | public void Advance(int count) 117 | { 118 | BufferSegment? current = _current; 119 | if (current is null) 120 | { 121 | throw new InvalidOperationException(); 122 | } 123 | if (count > current.AvailableLength) 124 | { 125 | throw new ArgumentOutOfRangeException(nameof(count)); 126 | } 127 | 128 | current.Advance(count); 129 | _length += count; 130 | } 131 | 132 | public byte[] ToArray() 133 | { 134 | int totalLength = 0; 135 | BufferSegment? segment = _head; 136 | while (segment != null) 137 | { 138 | totalLength += segment.Length; 139 | segment = segment.NextSegment; 140 | } 141 | 142 | byte[] destination = new byte[totalLength]; 143 | int offset = 0; 144 | segment = _head; 145 | while (segment != null) 146 | { 147 | segment.CopyTo(destination.AsSpan(offset)); 148 | offset += segment.Length; 149 | segment = segment.NextSegment; 150 | } 151 | 152 | return destination; 153 | } 154 | 155 | public void CopyTo(Span destination) 156 | { 157 | BufferSegment? segment = _head; 158 | while (segment != null) 159 | { 160 | segment.CopyTo(destination); 161 | destination = destination.Slice(segment.Length); 162 | segment = segment.NextSegment; 163 | } 164 | } 165 | 166 | public ReadOnlySequence GetReadOnlySequence() 167 | { 168 | if (_head is null || _current is null) 169 | { 170 | return ReadOnlySequence.Empty; 171 | } 172 | 173 | return new ReadOnlySequence(_head, 0, _current, _current.Length); 174 | } 175 | 176 | public void Dispose() 177 | { 178 | BufferSegment? segment = _head; 179 | while (segment != null) 180 | { 181 | segment = segment.ReturnMemory(); 182 | } 183 | _head = _current = null; 184 | _length = 0; 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /tests/JpegLibrary.Tests/Decoder/MetadataIdentifyTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using Xunit; 4 | 5 | namespace JpegLibrary.Tests.Decoder 6 | { 7 | public class MetadataIdentifyTests 8 | { 9 | public class Metadata 10 | { 11 | public int Width { get; set; } 12 | public int Height { get; set; } 13 | public int NumberOfComponents { get; set; } 14 | public int Precision { get; set; } 15 | public int Quality { get; set; } 16 | public int JpegStreamSize { get; set; } 17 | } 18 | 19 | public static IEnumerable GetIdentifyTestData() 20 | { 21 | string currentDir = Directory.GetCurrentDirectory(); 22 | yield return new object[] { 23 | Path.Join(currentDir, @"Assets/baseline/cramps.jpg"), 24 | new Metadata 25 | { 26 | Width = 800, 27 | Height = 607, 28 | NumberOfComponents = 1, 29 | Precision = 8, 30 | Quality = 90, 31 | JpegStreamSize = 137_766 32 | } 33 | }; 34 | yield return new object[] { 35 | Path.Join(currentDir, @"Assets/baseline/HETissueSlide.jpg"), 36 | new Metadata 37 | { 38 | Width = 2048, 39 | Height = 2048, 40 | NumberOfComponents = 3, 41 | Precision = 8, 42 | Quality = 75, 43 | JpegStreamSize = 783_426 44 | } 45 | }; 46 | yield return new object[] { 47 | Path.Join(currentDir, @"Assets/huffman_sequential/testorig12.jpg"), 48 | new Metadata 49 | { 50 | Width = 227, 51 | Height = 149, 52 | NumberOfComponents = 3, 53 | Precision = 12, 54 | Quality = 75, 55 | JpegStreamSize = 12_394 56 | } 57 | }; 58 | yield return new object[] { 59 | Path.Join(currentDir, @"Assets/huffman_progressive/yellowcat_progressive_restart.jpg"), 60 | new Metadata 61 | { 62 | Width = 720, 63 | Height = 540, 64 | NumberOfComponents = 3, 65 | Precision = 8, 66 | Quality = 75, 67 | JpegStreamSize = 45_703 68 | } 69 | }; 70 | yield return new object[] { 71 | Path.Join(currentDir, @"Assets/huffman_progressive/progress.jpg"), 72 | new Metadata 73 | { 74 | Width = 341, 75 | Height = 486, 76 | NumberOfComponents = 3, 77 | Precision = 8, 78 | Quality = 85, 79 | JpegStreamSize = 44_884 80 | } 81 | }; 82 | yield return new object[] { 83 | Path.Join(currentDir, @"Assets/huffman_lossless/lossless1_s22.jpg"), 84 | new Metadata 85 | { 86 | Width = 128, 87 | Height = 128, 88 | NumberOfComponents = 3, 89 | Precision = 8, 90 | Quality = 0, 91 | JpegStreamSize = 15_344 92 | } 93 | }; 94 | yield return new object[] { 95 | Path.Join(currentDir, @"Assets/arithmetic_sequential/yellowcat_arith_restart.jpg"), 96 | new Metadata 97 | { 98 | Width = 720, 99 | Height = 540, 100 | NumberOfComponents = 3, 101 | Precision = 8, 102 | Quality = 75, 103 | JpegStreamSize = 42_694 104 | } 105 | }; 106 | yield return new object[] { 107 | Path.Join(currentDir, @"Assets/arithmetic_progressive/yellowcat_progressive_arith.jpg"), 108 | new Metadata 109 | { 110 | Width = 720, 111 | Height = 540, 112 | NumberOfComponents = 3, 113 | Precision = 8, 114 | Quality = 75, 115 | JpegStreamSize = 42_260 116 | } 117 | }; 118 | yield return new object[] { 119 | Path.Join(currentDir, @"Assets/arithmetic_progressive/yellowcat_progressive_arith_restart.jpg"), 120 | new Metadata 121 | { 122 | Width = 720, 123 | Height = 540, 124 | NumberOfComponents = 3, 125 | Precision = 8, 126 | Quality = 75, 127 | JpegStreamSize = 42_526 128 | } 129 | }; 130 | } 131 | 132 | [Theory] 133 | [MemberData(nameof(GetIdentifyTestData))] 134 | public void TestDecoderIdentify(string path, Metadata data) 135 | { 136 | byte[] file = File.ReadAllBytes(path); 137 | var decoder = new JpegDecoder(); 138 | decoder.SetInput(file); 139 | int size = decoder.Identify(loadQuantizationTables: true); 140 | 141 | Assert.Equal(data.Width, decoder.Width); 142 | Assert.Equal(data.Height, decoder.Height); 143 | Assert.Equal(data.NumberOfComponents, decoder.NumberOfComponents); 144 | Assert.Equal(data.Precision, decoder.Precision); 145 | if (data.Quality > 0) 146 | { 147 | Assert.True(decoder.TryEstimateQuanlity(out float quality)); 148 | Assert.Equal(data.Quality, quality, 0); 149 | } 150 | if (data.JpegStreamSize > 0) 151 | { 152 | Assert.Equal(data.JpegStreamSize, size); 153 | } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /tests/Assets/arithmetic_sequential/zackthecat_arith.jpg.txt: -------------------------------------------------------------------------------- 1 | 2 | JPEGsnoop 1.8.0a by Calvin Hass 3 | http://www.impulseadventure.com/photo/ 4 | ------------------------------------- 5 | 6 | Filename: [C:\Users\jinyi\Documents\Repos\JpegLibrary\tests\Assets\arithmetic_sequential\zackthecat_arith.jpg] 7 | Filesize: [7753] Bytes 8 | 9 | Start Offset: 0x00000000 10 | *** Marker: SOI (xFFD8) *** 11 | OFFSET: 0x00000000 12 | 13 | *** Marker: APP0 (xFFE0) *** 14 | OFFSET: 0x00000002 15 | Length = 16 16 | Identifier = [JFIF] 17 | version = [1.1] 18 | density = 75 x 75 DPcm (dots per cm) 19 | thumbnail = 0 x 0 20 | 21 | *** Marker: DQT (xFFDB) *** 22 | Define a Quantization Table. 23 | OFFSET: 0x00000014 24 | Table length = 67 25 | ---- 26 | Precision=8 bits 27 | Destination ID=0 (Luminance) 28 | DQT, Row #0: 8 6 5 8 12 20 26 31 29 | DQT, Row #1: 6 6 7 10 13 29 30 28 30 | DQT, Row #2: 7 7 8 12 20 29 35 28 31 | DQT, Row #3: 7 9 11 15 26 44 40 31 32 | DQT, Row #4: 9 11 19 28 34 55 52 39 33 | DQT, Row #5: 12 18 28 32 41 52 57 46 34 | DQT, Row #6: 25 32 39 44 52 61 60 51 35 | DQT, Row #7: 36 46 48 49 56 50 52 50 36 | Approx quality factor = 74.75 (scaling=50.51 variance=0.81) 37 | 38 | *** Marker: DQT (xFFDB) *** 39 | Define a Quantization Table. 40 | OFFSET: 0x00000059 41 | Table length = 67 42 | ---- 43 | Precision=8 bits 44 | Destination ID=1 (Chrominance) 45 | DQT, Row #0: 9 9 12 24 50 50 50 50 46 | DQT, Row #1: 9 11 13 33 50 50 50 50 47 | DQT, Row #2: 12 13 28 50 50 50 50 50 48 | DQT, Row #3: 24 33 50 50 50 50 50 50 49 | DQT, Row #4: 50 50 50 50 50 50 50 50 50 | DQT, Row #5: 50 50 50 50 50 50 50 50 51 | DQT, Row #6: 50 50 50 50 50 50 50 50 52 | DQT, Row #7: 50 50 50 50 50 50 50 50 53 | Approx quality factor = 74.74 (scaling=50.52 variance=0.19) 54 | 55 | *** Marker: SOF9 (Sequential DCT, Arithmetic) (xFFC9) *** 56 | OFFSET: 0x0000009E 57 | Frame header length = 17 58 | Precision = 8 59 | Number of Lines = 213 60 | Samples per Line = 234 61 | Image Size = 234 x 213 62 | Raw Image Orientation = Landscape 63 | Number of Img components = 3 64 | Component[1]: ID=0x01, Samp Fac=0x22 (Subsamp 1 x 1), Quant Tbl Sel=0x00 (Lum: Y) 65 | Component[2]: ID=0x02, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cb) 66 | Component[3]: ID=0x03, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cr) 67 | 68 | *** Marker: DAC (xFFCC) *** 69 | OFFSET: 0x000000B1 70 | Arithmetic coding header length = 10 71 | #01: Table class = 0 72 | #01: Table destination identifier = 0 73 | #01: Conditioning table value = 16 74 | #02: Table class = 1 75 | #02: Table destination identifier = 0 76 | #02: Conditioning table value = 5 77 | #03: Table class = 0 78 | #03: Table destination identifier = 1 79 | #03: Conditioning table value = 16 80 | #04: Table class = 1 81 | #04: Table destination identifier = 1 82 | #04: Conditioning table value = 5 83 | 84 | *** Marker: SOS (Start of Scan) (xFFDA) *** 85 | OFFSET: 0x000000BD 86 | Scan header length = 12 87 | Number of img components = 3 88 | Component[1]: selector=0x01, table=0(DC),0(AC) 89 | Component[2]: selector=0x02, table=1(DC),1(AC) 90 | Component[3]: selector=0x03, table=1(DC),1(AC) 91 | Spectral selection = 0 .. 63 92 | Successive approximation = 0x00 93 | 94 | NOTE: Scan parsing doesn't support this SOF mode. 95 | 96 | *** Marker: EOI (End of Image) (xFFD9) *** 97 | OFFSET: 0x00001E47 98 | 99 | 100 | *** Searching Compression Signatures *** 101 | 102 | Signature: 0182408A81A4ABF04D4A34A8A5E98C58 103 | Signature (Rotated): 012D821C6AB210E2A753BE053B8F55D0 104 | File Offset: 0 bytes 105 | Chroma subsampling: 2x2 106 | EXIF Make/Model: NONE 107 | EXIF Makernotes: NONE 108 | EXIF Software: NONE 109 | 110 | Searching Compression Signatures: (3347 built-in, 0 user(*) ) 111 | 112 | EXIF.Make / Software EXIF.Model Quality Subsamp Match? 113 | ------------------------- ----------------------------------- ---------------- -------------- 114 | CAM:[SONY ] [CYBERSHOT U ] [ ] Yes 115 | SW :[Adobe Photoshop 7.0 ] [Save As 07 ] 116 | SW :[Apple Quicktime ] [0466-0467 ] 117 | SW :[Digital Photo Professiona] [05 ] 118 | SW :[IJG Library ] [075 ] 119 | SW :[MS Paint ] [ ] 120 | SW :[MS Visio ] [ ] 121 | SW :[ZoomBrowser EX ] [low ] 122 | 123 | The following IJG-based editors also match this signature: 124 | SW :[GIMP ] [075 ] 125 | SW :[IrfanView ] [075 ] 126 | SW :[idImager ] [075 ] 127 | SW :[FastStone Image Viewer ] [075 ] 128 | SW :[NeatImage ] [075 ] 129 | SW :[Paint.NET ] [075 ] 130 | SW :[Photomatix ] [075 ] 131 | SW :[XnView ] [075 ] 132 | 133 | Based on the analysis of compression characteristics and EXIF metadata: 134 | 135 | ASSESSMENT: Class 1 - Image is processed/edited 136 | 137 | This may be a new software editor for the database. 138 | If this file is processed, and editor doesn't appear in list above, 139 | PLEASE ADD TO DATABASE with [Tools->Add Camera to DB] 140 | 141 | -------------------------------------------------------------------------------- /tests/Assets/arithmetic_sequential/yellowcat_arith_restart.jpg.txt: -------------------------------------------------------------------------------- 1 | 2 | JPEGsnoop 1.8.0a by Calvin Hass 3 | http://www.impulseadventure.com/photo/ 4 | ------------------------------------- 5 | 6 | Filename: [C:\Users\jinyi\Documents\Repos\JpegLibrary\tests\Assets\arithmetic_progressive\yellowcat_arith_restart.jpg] 7 | Filesize: [42694] Bytes 8 | 9 | Start Offset: 0x00000000 10 | *** Marker: SOI (xFFD8) *** 11 | OFFSET: 0x00000000 12 | 13 | *** Marker: APP0 (xFFE0) *** 14 | OFFSET: 0x00000002 15 | Length = 16 16 | Identifier = [JFIF] 17 | version = [1.1] 18 | density = 75 x 75 DPcm (dots per cm) 19 | thumbnail = 0 x 0 20 | 21 | *** Marker: DQT (xFFDB) *** 22 | Define a Quantization Table. 23 | OFFSET: 0x00000014 24 | Table length = 67 25 | ---- 26 | Precision=8 bits 27 | Destination ID=0 (Luminance) 28 | DQT, Row #0: 8 6 5 8 12 20 26 31 29 | DQT, Row #1: 6 6 7 10 13 29 30 28 30 | DQT, Row #2: 7 7 8 12 20 29 35 28 31 | DQT, Row #3: 7 9 11 15 26 44 40 31 32 | DQT, Row #4: 9 11 19 28 34 55 52 39 33 | DQT, Row #5: 12 18 28 32 41 52 57 46 34 | DQT, Row #6: 25 32 39 44 52 61 60 51 35 | DQT, Row #7: 36 46 48 49 56 50 52 50 36 | Approx quality factor = 74.75 (scaling=50.51 variance=0.81) 37 | 38 | *** Marker: DQT (xFFDB) *** 39 | Define a Quantization Table. 40 | OFFSET: 0x00000059 41 | Table length = 67 42 | ---- 43 | Precision=8 bits 44 | Destination ID=1 (Chrominance) 45 | DQT, Row #0: 9 9 12 24 50 50 50 50 46 | DQT, Row #1: 9 11 13 33 50 50 50 50 47 | DQT, Row #2: 12 13 28 50 50 50 50 50 48 | DQT, Row #3: 24 33 50 50 50 50 50 50 49 | DQT, Row #4: 50 50 50 50 50 50 50 50 50 | DQT, Row #5: 50 50 50 50 50 50 50 50 51 | DQT, Row #6: 50 50 50 50 50 50 50 50 52 | DQT, Row #7: 50 50 50 50 50 50 50 50 53 | Approx quality factor = 74.74 (scaling=50.52 variance=0.19) 54 | 55 | *** Marker: SOF9 (Sequential DCT, Arithmetic) (xFFC9) *** 56 | OFFSET: 0x0000009E 57 | Frame header length = 17 58 | Precision = 8 59 | Number of Lines = 540 60 | Samples per Line = 720 61 | Image Size = 720 x 540 62 | Raw Image Orientation = Landscape 63 | Number of Img components = 3 64 | Component[1]: ID=0x01, Samp Fac=0x22 (Subsamp 1 x 1), Quant Tbl Sel=0x00 (Lum: Y) 65 | Component[2]: ID=0x02, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cb) 66 | Component[3]: ID=0x03, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cr) 67 | 68 | *** Marker: DAC (xFFCC) *** 69 | OFFSET: 0x000000B1 70 | Arithmetic coding header length = 10 71 | #01: Table class = 0 72 | #01: Table destination identifier = 0 73 | #01: Conditioning table value = 16 74 | #02: Table class = 1 75 | #02: Table destination identifier = 0 76 | #02: Conditioning table value = 5 77 | #03: Table class = 0 78 | #03: Table destination identifier = 1 79 | #03: Conditioning table value = 16 80 | #04: Table class = 1 81 | #04: Table destination identifier = 1 82 | #04: Conditioning table value = 5 83 | 84 | *** Marker: DRI (Restart Interval) (xFFDD) *** 85 | OFFSET: 0x000000BD 86 | Length = 4 87 | interval = 720 88 | 89 | *** Marker: SOS (Start of Scan) (xFFDA) *** 90 | OFFSET: 0x000000C3 91 | Scan header length = 12 92 | Number of img components = 3 93 | Component[1]: selector=0x01, table=0(DC),0(AC) 94 | Component[2]: selector=0x02, table=1(DC),1(AC) 95 | Component[3]: selector=0x03, table=1(DC),1(AC) 96 | Spectral selection = 0 .. 63 97 | Successive approximation = 0x00 98 | 99 | NOTE: Scan parsing doesn't support this SOF mode. 100 | 101 | *** Marker: EOI (End of Image) (xFFD9) *** 102 | OFFSET: 0x0000A6C4 103 | 104 | 105 | *** Searching Compression Signatures *** 106 | 107 | Signature: 0182408A81A4ABF04D4A34A8A5E98C58 108 | Signature (Rotated): 012D821C6AB210E2A753BE053B8F55D0 109 | File Offset: 0 bytes 110 | Chroma subsampling: 2x2 111 | EXIF Make/Model: NONE 112 | EXIF Makernotes: NONE 113 | EXIF Software: NONE 114 | 115 | Searching Compression Signatures: (3347 built-in, 0 user(*) ) 116 | 117 | EXIF.Make / Software EXIF.Model Quality Subsamp Match? 118 | ------------------------- ----------------------------------- ---------------- -------------- 119 | CAM:[SONY ] [CYBERSHOT U ] [ ] Yes 120 | SW :[Adobe Photoshop 7.0 ] [Save As 07 ] 121 | SW :[Apple Quicktime ] [0466-0467 ] 122 | SW :[Digital Photo Professiona] [05 ] 123 | SW :[IJG Library ] [075 ] 124 | SW :[MS Paint ] [ ] 125 | SW :[MS Visio ] [ ] 126 | SW :[ZoomBrowser EX ] [low ] 127 | 128 | The following IJG-based editors also match this signature: 129 | SW :[GIMP ] [075 ] 130 | SW :[IrfanView ] [075 ] 131 | SW :[idImager ] [075 ] 132 | SW :[FastStone Image Viewer ] [075 ] 133 | SW :[NeatImage ] [075 ] 134 | SW :[Paint.NET ] [075 ] 135 | SW :[Photomatix ] [075 ] 136 | SW :[XnView ] [075 ] 137 | 138 | Based on the analysis of compression characteristics and EXIF metadata: 139 | 140 | ASSESSMENT: Class 1 - Image is processed/edited 141 | 142 | This may be a new software editor for the database. 143 | If this file is processed, and editor doesn't appear in list above, 144 | PLEASE ADD TO DATABASE with [Tools->Add Camera to DB] 145 | 146 | -------------------------------------------------------------------------------- /tests/Assets/arithmetic_sequential/zackthecat_arith_restart.jpg.txt: -------------------------------------------------------------------------------- 1 | 2 | JPEGsnoop 1.8.0a by Calvin Hass 3 | http://www.impulseadventure.com/photo/ 4 | ------------------------------------- 5 | 6 | Filename: [C:\Users\jinyi\Documents\Repos\JpegLibrary\tests\Assets\arithmetic_sequential\zackthecat_arith_restart.jpg] 7 | Filesize: [7759] Bytes 8 | 9 | Start Offset: 0x00000000 10 | *** Marker: SOI (xFFD8) *** 11 | OFFSET: 0x00000000 12 | 13 | *** Marker: APP0 (xFFE0) *** 14 | OFFSET: 0x00000002 15 | Length = 16 16 | Identifier = [JFIF] 17 | version = [1.1] 18 | density = 75 x 75 DPcm (dots per cm) 19 | thumbnail = 0 x 0 20 | 21 | *** Marker: DQT (xFFDB) *** 22 | Define a Quantization Table. 23 | OFFSET: 0x00000014 24 | Table length = 67 25 | ---- 26 | Precision=8 bits 27 | Destination ID=0 (Luminance) 28 | DQT, Row #0: 8 6 5 8 12 20 26 31 29 | DQT, Row #1: 6 6 7 10 13 29 30 28 30 | DQT, Row #2: 7 7 8 12 20 29 35 28 31 | DQT, Row #3: 7 9 11 15 26 44 40 31 32 | DQT, Row #4: 9 11 19 28 34 55 52 39 33 | DQT, Row #5: 12 18 28 32 41 52 57 46 34 | DQT, Row #6: 25 32 39 44 52 61 60 51 35 | DQT, Row #7: 36 46 48 49 56 50 52 50 36 | Approx quality factor = 74.75 (scaling=50.51 variance=0.81) 37 | 38 | *** Marker: DQT (xFFDB) *** 39 | Define a Quantization Table. 40 | OFFSET: 0x00000059 41 | Table length = 67 42 | ---- 43 | Precision=8 bits 44 | Destination ID=1 (Chrominance) 45 | DQT, Row #0: 9 9 12 24 50 50 50 50 46 | DQT, Row #1: 9 11 13 33 50 50 50 50 47 | DQT, Row #2: 12 13 28 50 50 50 50 50 48 | DQT, Row #3: 24 33 50 50 50 50 50 50 49 | DQT, Row #4: 50 50 50 50 50 50 50 50 50 | DQT, Row #5: 50 50 50 50 50 50 50 50 51 | DQT, Row #6: 50 50 50 50 50 50 50 50 52 | DQT, Row #7: 50 50 50 50 50 50 50 50 53 | Approx quality factor = 74.74 (scaling=50.52 variance=0.19) 54 | 55 | *** Marker: SOF9 (Sequential DCT, Arithmetic) (xFFC9) *** 56 | OFFSET: 0x0000009E 57 | Frame header length = 17 58 | Precision = 8 59 | Number of Lines = 213 60 | Samples per Line = 234 61 | Image Size = 234 x 213 62 | Raw Image Orientation = Landscape 63 | Number of Img components = 3 64 | Component[1]: ID=0x01, Samp Fac=0x22 (Subsamp 1 x 1), Quant Tbl Sel=0x00 (Lum: Y) 65 | Component[2]: ID=0x02, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cb) 66 | Component[3]: ID=0x03, Samp Fac=0x11 (Subsamp 2 x 2), Quant Tbl Sel=0x01 (Chrom: Cr) 67 | 68 | *** Marker: DAC (xFFCC) *** 69 | OFFSET: 0x000000B1 70 | Arithmetic coding header length = 10 71 | #01: Table class = 0 72 | #01: Table destination identifier = 0 73 | #01: Conditioning table value = 16 74 | #02: Table class = 1 75 | #02: Table destination identifier = 0 76 | #02: Conditioning table value = 5 77 | #03: Table class = 0 78 | #03: Table destination identifier = 1 79 | #03: Conditioning table value = 16 80 | #04: Table class = 1 81 | #04: Table destination identifier = 1 82 | #04: Conditioning table value = 5 83 | 84 | *** Marker: DRI (Restart Interval) (xFFDD) *** 85 | OFFSET: 0x000000BD 86 | Length = 4 87 | interval = 240 88 | 89 | *** Marker: SOS (Start of Scan) (xFFDA) *** 90 | OFFSET: 0x000000C3 91 | Scan header length = 12 92 | Number of img components = 3 93 | Component[1]: selector=0x01, table=0(DC),0(AC) 94 | Component[2]: selector=0x02, table=1(DC),1(AC) 95 | Component[3]: selector=0x03, table=1(DC),1(AC) 96 | Spectral selection = 0 .. 63 97 | Successive approximation = 0x00 98 | 99 | NOTE: Scan parsing doesn't support this SOF mode. 100 | 101 | *** Marker: EOI (End of Image) (xFFD9) *** 102 | OFFSET: 0x00001E4D 103 | 104 | 105 | *** Searching Compression Signatures *** 106 | 107 | Signature: 0182408A81A4ABF04D4A34A8A5E98C58 108 | Signature (Rotated): 012D821C6AB210E2A753BE053B8F55D0 109 | File Offset: 0 bytes 110 | Chroma subsampling: 2x2 111 | EXIF Make/Model: NONE 112 | EXIF Makernotes: NONE 113 | EXIF Software: NONE 114 | 115 | Searching Compression Signatures: (3347 built-in, 0 user(*) ) 116 | 117 | EXIF.Make / Software EXIF.Model Quality Subsamp Match? 118 | ------------------------- ----------------------------------- ---------------- -------------- 119 | CAM:[SONY ] [CYBERSHOT U ] [ ] Yes 120 | SW :[Adobe Photoshop 7.0 ] [Save As 07 ] 121 | SW :[Apple Quicktime ] [0466-0467 ] 122 | SW :[Digital Photo Professiona] [05 ] 123 | SW :[IJG Library ] [075 ] 124 | SW :[MS Paint ] [ ] 125 | SW :[MS Visio ] [ ] 126 | SW :[ZoomBrowser EX ] [low ] 127 | 128 | The following IJG-based editors also match this signature: 129 | SW :[GIMP ] [075 ] 130 | SW :[IrfanView ] [075 ] 131 | SW :[idImager ] [075 ] 132 | SW :[FastStone Image Viewer ] [075 ] 133 | SW :[NeatImage ] [075 ] 134 | SW :[Paint.NET ] [075 ] 135 | SW :[Photomatix ] [075 ] 136 | SW :[XnView ] [075 ] 137 | 138 | Based on the analysis of compression characteristics and EXIF metadata: 139 | 140 | ASSESSMENT: Class 1 - Image is processed/edited 141 | 142 | This may be a new software editor for the database. 143 | If this file is processed, and editor doesn't appear in list above, 144 | PLEASE ADD TO DATABASE with [Tools->Add Camera to DB] 145 | 146 | --------------------------------------------------------------------------------