From 9cbb32112a2b2198bec478777e20cc9647ea5cc5 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 19 Jul 2016 13:02:42 +1000 Subject: [PATCH 1/8] Quality should be copied. Former-commit-id: e30a7b839d512f4a5faa85d16b23d421f2325e4c Former-commit-id: a49e33c994d79bf4092788ef3027c1f47c2f66c4 Former-commit-id: 15bc1d0c90ebca357322e1ba10c21c3830a8e4ae --- src/ImageProcessorCore/Image.cs | 1 + src/ImageProcessorCore/ImageExtensions.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ImageProcessorCore/Image.cs b/src/ImageProcessorCore/Image.cs index 3cde69543..ec27e457b 100644 --- a/src/ImageProcessorCore/Image.cs +++ b/src/ImageProcessorCore/Image.cs @@ -73,6 +73,7 @@ namespace ImageProcessorCore } } + this.Quality = other.Quality; this.RepeatCount = other.RepeatCount; this.HorizontalResolution = other.HorizontalResolution; this.VerticalResolution = other.VerticalResolution; diff --git a/src/ImageProcessorCore/ImageExtensions.cs b/src/ImageProcessorCore/ImageExtensions.cs index e7b261d60..ea320aad4 100644 --- a/src/ImageProcessorCore/ImageExtensions.cs +++ b/src/ImageProcessorCore/ImageExtensions.cs @@ -116,7 +116,7 @@ namespace ImageProcessorCore /// The . public static Image Process(this Image source, int width, int height, Rectangle sourceRectangle, Rectangle targetRectangle, IImageSampler sampler) { - return PerformAction(source, false, (sourceImage, targetImage) => sampler.Apply(targetImage, sourceImage, width, height, targetRectangle, sourceRectangle)); + return PerformAction(source, false, (sourceImage, targetImage) => sampler.Apply(targetImage, sourceImage, width, height, targetRectangle, sourceRectangle)); } /// @@ -135,6 +135,7 @@ namespace ImageProcessorCore { // Several properties require copying // TODO: Check why we need to set these? + Quality = source.Quality, HorizontalResolution = source.HorizontalResolution, VerticalResolution = source.VerticalResolution, CurrentImageFormat = source.CurrentImageFormat, From 20af21aec408417f76779798cc272dc8c7bac54f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 20 Jul 2016 17:52:55 +1000 Subject: [PATCH 2/8] Clean up block and FDCT Former-commit-id: af0313bdaeecaa56e576f25a1fa688a333468759 Former-commit-id: 64319b3f8faaa68997ce43ffb284a1d5d4937a78 Former-commit-id: 971cc28d2e1a019c99800f231c3451c89d1bca49 --- src/ImageProcessorCore/Formats/Jpg/Block.cs | 39 +++- src/ImageProcessorCore/Formats/Jpg/FDCT.cs | 106 ++++++----- .../Formats/Jpg/JpegConstants.cs | 171 ++++++++++++++++++ .../Jpg/JpegDecoderCore.cs.REMOVED.git-id | 2 +- .../Formats/Jpg/JpegEncoderCore.cs | 8 +- 5 files changed, 271 insertions(+), 55 deletions(-) create mode 100644 src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs diff --git a/src/ImageProcessorCore/Formats/Jpg/Block.cs b/src/ImageProcessorCore/Formats/Jpg/Block.cs index 655471a07..35aa10f18 100644 --- a/src/ImageProcessorCore/Formats/Jpg/Block.cs +++ b/src/ImageProcessorCore/Formats/Jpg/Block.cs @@ -1,19 +1,44 @@ -namespace ImageProcessorCore.Formats +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats { + /// + /// Represents an 8x8 block of coefficients to transform and encode. + /// internal class Block { - public const int blockSize = 64; - private int[] _data; + /// + /// Gets the size of the block. + /// + public const int BlockSize = 64; + + /// + /// The array of block data. + /// + private readonly int[] data; + /// + /// Initializes a new instance of the class. + /// public Block() { - _data = new int[blockSize]; + this.data = new int[BlockSize]; } - public int this[int idx] + /// + /// Gets the pixel data at the given block index. + /// + /// The index of the data to return. + /// + /// The . + /// + public int this[int index] { - get { return _data[idx]; } - set { _data[idx] = value; } + get { return this.data[index]; } + set { this.data[index] = value; } } } } diff --git a/src/ImageProcessorCore/Formats/Jpg/FDCT.cs b/src/ImageProcessorCore/Formats/Jpg/FDCT.cs index a6073f9c7..e51ea6415 100644 --- a/src/ImageProcessorCore/Formats/Jpg/FDCT.cs +++ b/src/ImageProcessorCore/Formats/Jpg/FDCT.cs @@ -5,9 +5,14 @@ namespace ImageProcessorCore.Formats { + /// + /// Performs a fast, forward descrete cosine transform against the given block + /// decomposing it into 64 orthogonal basis signals. + /// internal class FDCT { // Trigonometric constants in 13-bit fixed point format. + // TODO: Rename and describe these. private const int fix_0_298631336 = 2446; private const int fix_0_390180644 = 3196; private const int fix_0_541196100 = 4433; @@ -20,27 +25,42 @@ namespace ImageProcessorCore.Formats private const int fix_2_053119869 = 16819; private const int fix_2_562915447 = 20995; private const int fix_3_072711026 = 25172; - private const int constBits = 13; - private const int pass1Bits = 2; - private const int centerJSample = 128; - // fdct performs a forward DCT on an 8x8 block of coefficients, including a - // level shift. - public static void Transform(Block b) + /// + /// The number of bits + /// + private const int Bits = 13; + + /// + /// The number of bits to shift by on the first pass. + /// + private const int Pass1Bits = 2; + + /// + /// The value to shift by + /// + private const int CenterJSample = 128; + + /// + /// Performs a forward DCT on an 8x8 block of coefficients, including a + /// level shift. + /// + /// The block. + public static void Transform(Block block) { // Pass 1: process rows. for (int y = 0; y < 8; y++) { int y8 = y * 8; - int x0 = b[y8 + 0]; - int x1 = b[y8 + 1]; - int x2 = b[y8 + 2]; - int x3 = b[y8 + 3]; - int x4 = b[y8 + 4]; - int x5 = b[y8 + 5]; - int x6 = b[y8 + 6]; - int x7 = b[y8 + 7]; + int x0 = block[y8]; + int x1 = block[y8 + 1]; + int x2 = block[y8 + 2]; + int x3 = block[y8 + 3]; + int x4 = block[y8 + 4]; + int x5 = block[y8 + 5]; + int x6 = block[y8 + 6]; + int x7 = block[y8 + 7]; int tmp0 = x0 + x7; int tmp1 = x1 + x6; @@ -57,19 +77,19 @@ namespace ImageProcessorCore.Formats tmp2 = x2 - x5; tmp3 = x3 - x4; - b[y8] = (tmp10 + tmp11 - 8 * centerJSample) << pass1Bits; - b[y8 + 4] = (tmp10 - tmp11) << pass1Bits; + block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits; + block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits; int z1 = (tmp12 + tmp13) * fix_0_541196100; - z1 += 1 << (constBits - pass1Bits - 1); - b[y8 + 2] = (z1 + tmp12 * fix_0_765366865) >> (constBits - pass1Bits); - b[y8 + 6] = (z1 - tmp13 * fix_1_847759065) >> (constBits - pass1Bits); + z1 += 1 << (Bits - Pass1Bits - 1); + block[y8 + 2] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits - Pass1Bits); + block[y8 + 6] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits - Pass1Bits); tmp10 = tmp0 + tmp3; tmp11 = tmp1 + tmp2; tmp12 = tmp0 + tmp2; tmp13 = tmp1 + tmp3; z1 = (tmp12 + tmp13) * fix_1_175875602; - z1 += 1 << (constBits - pass1Bits - 1); + z1 += 1 << (Bits - Pass1Bits - 1); tmp0 = tmp0 * fix_1_501321110; tmp1 = tmp1 * fix_3_072711026; tmp2 = tmp2 * fix_2_053119869; @@ -81,45 +101,45 @@ namespace ImageProcessorCore.Formats tmp12 += z1; tmp13 += z1; - b[y8 + 1] = (tmp0 + tmp10 + tmp12) >> (constBits - pass1Bits); - b[y8 + 3] = (tmp1 + tmp11 + tmp13) >> (constBits - pass1Bits); - b[y8 + 5] = (tmp2 + tmp11 + tmp12) >> (constBits - pass1Bits); - b[y8 + 7] = (tmp3 + tmp10 + tmp13) >> (constBits - pass1Bits); + block[y8 + 1] = (tmp0 + tmp10 + tmp12) >> (Bits - Pass1Bits); + block[y8 + 3] = (tmp1 + tmp11 + tmp13) >> (Bits - Pass1Bits); + block[y8 + 5] = (tmp2 + tmp11 + tmp12) >> (Bits - Pass1Bits); + block[y8 + 7] = (tmp3 + tmp10 + tmp13) >> (Bits - Pass1Bits); } // Pass 2: process columns. // We remove pass1Bits scaling, but leave results scaled up by an overall factor of 8. for (int x = 0; x < 8; x++) { - int tmp0 = b[x] + b[56 + x]; - int tmp1 = b[8 + x] + b[48 + x]; - int tmp2 = b[16 + x] + b[40 + x]; - int tmp3 = b[24 + x] + b[32 + x]; + int tmp0 = block[x] + block[56 + x]; + int tmp1 = block[8 + x] + block[48 + x]; + int tmp2 = block[16 + x] + block[40 + x]; + int tmp3 = block[24 + x] + block[32 + x]; - int tmp10 = tmp0 + tmp3 + (1 << (pass1Bits - 1)); + int tmp10 = tmp0 + tmp3 + (1 << (Pass1Bits - 1)); int tmp12 = tmp0 - tmp3; int tmp11 = tmp1 + tmp2; int tmp13 = tmp1 - tmp2; - tmp0 = b[x] - b[56 + x]; - tmp1 = b[8 + x] - b[48 + x]; - tmp2 = b[16 + x] - b[40 + x]; - tmp3 = b[24 + x] - b[32 + x]; + tmp0 = block[x] - block[56 + x]; + tmp1 = block[8 + x] - block[48 + x]; + tmp2 = block[16 + x] - block[40 + x]; + tmp3 = block[24 + x] - block[32 + x]; - b[x] = (tmp10 + tmp11) >> pass1Bits; - b[32 + x] = (tmp10 - tmp11) >> pass1Bits; + block[x] = (tmp10 + tmp11) >> Pass1Bits; + block[32 + x] = (tmp10 - tmp11) >> Pass1Bits; int z1 = (tmp12 + tmp13) * fix_0_541196100; - z1 += 1 << (constBits + pass1Bits - 1); - b[16 + x] = (z1 + tmp12 * fix_0_765366865) >> (constBits + pass1Bits); - b[48 + x] = (z1 - tmp13 * fix_1_847759065) >> (constBits + pass1Bits); + z1 += 1 << (Bits + Pass1Bits - 1); + block[16 + x] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits + Pass1Bits); + block[48 + x] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits + Pass1Bits); tmp10 = tmp0 + tmp3; tmp11 = tmp1 + tmp2; tmp12 = tmp0 + tmp2; tmp13 = tmp1 + tmp3; z1 = (tmp12 + tmp13) * fix_1_175875602; - z1 += 1 << (constBits + pass1Bits - 1); + z1 += 1 << (Bits + Pass1Bits - 1); tmp0 = tmp0 * fix_1_501321110; tmp1 = tmp1 * fix_3_072711026; tmp2 = tmp2 * fix_2_053119869; @@ -131,10 +151,10 @@ namespace ImageProcessorCore.Formats tmp12 += z1; tmp13 += z1; - b[8 + x] = (tmp0 + tmp10 + tmp12) >> (constBits + pass1Bits); - b[24 + x] = (tmp1 + tmp11 + tmp13) >> (constBits + pass1Bits); - b[40 + x] = (tmp2 + tmp11 + tmp12) >> (constBits + pass1Bits); - b[56 + x] = (tmp3 + tmp10 + tmp13) >> (constBits + pass1Bits); + block[8 + x] = (tmp0 + tmp10 + tmp12) >> (Bits + Pass1Bits); + block[24 + x] = (tmp1 + tmp11 + tmp13) >> (Bits + Pass1Bits); + block[40 + x] = (tmp2 + tmp11 + tmp12) >> (Bits + Pass1Bits); + block[56 + x] = (tmp3 + tmp10 + tmp13) >> (Bits + Pass1Bits); } } } diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs b/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs new file mode 100644 index 000000000..27514d667 --- /dev/null +++ b/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs @@ -0,0 +1,171 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Formats +{ + /// + /// Defines jpeg constants defined in the specification. + /// + internal static class JpegConstants + { + /// + /// The maximum allowable length in each dimension of a jpeg image. + /// + public const ushort MaxLength = 65535; + + /// + /// Represents high detail chroma horizontal subsampling. + /// + public static readonly byte[] ChromaFourFourFourHorizontal = { 1, 1, 1 }; + + /// + /// Represents high detail chroma vertical subsampling. + /// + public static readonly byte[] ChromaFourFourFourVertical = { 1, 1, 1 }; + + /// + /// Represents medium detail chroma horizontal subsampling. + /// + public static readonly byte[] ChromaFourTwoTwoHorizontal = { 2, 1, 1 }; + + /// + /// Represents medium detail chroma vertical subsampling. + /// + public static readonly byte[] ChromaFourTwoTwoVertical = { 1, 1, 1 }; + + /// + /// Represents low detail chroma horizontal subsampling. + /// + public static readonly byte[] ChromaFourTwoZeroHorizontal = { 2, 1, 1 }; + + /// + /// Represents low detail chroma vertical subsampling. + /// + public static readonly byte[] ChromaFourTwoZeroVertical = { 2, 1, 1 }; + + /// + /// Describes component ids for start of frame components. + /// + internal static class Components + { + /// + /// The YCbCr luminance component id. + /// + public const byte Y = 1; + + /// + /// The YCbCr chroma component id. + /// + public const byte Cb = 2; + + /// + /// The YCbCr chroma component id. + /// + public const byte Cr = 3; + + /// + /// The YIQ x coordinate component id. + /// + public const byte I = 4; + + /// + /// The YIQ y coordinate component id. + /// + public const byte Q = 5; + } + + /// + /// Describes common Jpeg markers + /// + internal static class Markers + { + /// + /// Marker prefix. Next byte is a marker. + /// + public const byte XFF = 0xff; + + /// + /// Start of Image + /// + public const byte SOI = 0xd8; + + /// + /// Start of Frame (baseline DCT) + /// + /// Indicates that this is a baseline DCT-based JPEG, and specifies the width, height, number of components, + /// and component subsampling (e.g., 4:2:0). + /// + /// + public const byte SOF0 = 0xc0; + + /// + /// Start Of Frame (progressive DCT) + /// + /// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components, + /// and component subsampling (e.g., 4:2:0). + /// + /// + public const byte SOF2 = 0xc0; + + /// + /// Define Huffman Table(s) + /// + /// Specifies one or more Huffman tables. + /// + /// + public const byte DHT = 0xc4; + + /// + /// Define Quantization Table(s) + /// + /// Specifies one or more quantization tables. + /// + /// + public const byte DQT = 0xdb; + + /// + /// Define Restart Interval + /// + /// Specifies the interval between RSTn markers, in macroblocks. This marker is followed by two bytes + /// indicating the fixed size so it can be treated like any other variable size segment. + /// + /// + public const byte DRI = 0xdd; + + /// + /// Start of Scan + /// + /// Begins a top-to-bottom scan of the image. In baseline DCT JPEG images, there is generally a single scan. + /// Progressive DCT JPEG images usually contain multiple scans. This marker specifies which slice of data it + /// will contain, and is immediately followed by entropy-coded data. + /// + /// + public const byte SOS = 0xda; + + /// + /// Comment + /// + /// Contains a text comment. + /// + /// + public const byte COM = 0xfe; + + /// + /// End of Image + /// + public const byte EOI = 0xd9; + + /// + /// Application specific marker for marking the jpeg format. + /// + public const byte APP0 = 0xe0; + + /// + /// Application specific marker for marking where to store metadata. + /// + public const byte APP1 = 0xe1; + } + } +} \ No newline at end of file diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id index 6cf3cee55..8d3e51867 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id +++ b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id @@ -1 +1 @@ -07d53b1f9ee1e867cfd8bb664c2cf3f31c0e8289 \ No newline at end of file +af4a353d1d290be5dd231656bdb558fcdc143805 \ No newline at end of file diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs index a1f49a0c8..f211e05f8 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs @@ -298,7 +298,7 @@ namespace ImageProcessorCore.Formats // writeDQT writes the Define Quantization Table marker. private void writeDQT() { - int markerlen = 2 + nQuantIndex * (1 + Block.blockSize); + int markerlen = 2 + nQuantIndex * (1 + Block.BlockSize); writeMarkerHeader(dqtMarker, markerlen); for (int i = 0; i < nQuantIndex; i++) { @@ -396,7 +396,7 @@ namespace ImageProcessorCore.Formats var h = (huffIndex)(2 * (int)q + 1); int runLength = 0; - for (int zig = 1; zig < Block.blockSize; zig++) + for (int zig = 1; zig < Block.BlockSize; zig++) { int ac = div(b[unzig[zig]], 8 * quant[(int)q][zig]); @@ -567,7 +567,7 @@ namespace ImageProcessorCore.Formats for (int i = 0; i < nQuantIndex; i++) { - quant[i] = new byte[Block.blockSize]; + quant[i] = new byte[Block.BlockSize]; } if (image.Width >= (1 << 16) || image.Height >= (1 << 16)) @@ -592,7 +592,7 @@ namespace ImageProcessorCore.Formats // Initialize the quantization tables. for (int i = 0; i < nQuantIndex; i++) { - for (int j = 0; j < Block.blockSize; j++) + for (int j = 0; j < Block.BlockSize; j++) { int x = unscaledQuant[i, j]; x = (x * scale + 50) / 100; From dda14207805d697463d025db98119d44ddefe767 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 20 Jul 2016 18:20:41 +1000 Subject: [PATCH 3/8] Use qualifier Former-commit-id: e7f5254e6a05eef6e2c41a7113ec43117419f3ad Former-commit-id: d8fb7b19aa30b69f2c83f6e5e93a965f0e95a9ae Former-commit-id: 07ec0c3ba4120079b88203fbc98e3d73df7080bf --- .../Formats/Jpg/JpegEncoderCore.cs | 485 ++++++++++-------- 1 file changed, 258 insertions(+), 227 deletions(-) diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs index f211e05f8..3274907d5 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs @@ -2,7 +2,6 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // - namespace ImageProcessorCore.Formats { using System; @@ -11,173 +10,194 @@ namespace ImageProcessorCore.Formats internal class JpegEncoderCore { private const int sof0Marker = 0xc0; // Start Of Frame (Baseline). + private const int sof1Marker = 0xc1; // Start Of Frame (Extended Sequential). + private const int sof2Marker = 0xc2; // Start Of Frame (Progressive). + private const int dhtMarker = 0xc4; // Define Huffman Table. + private const int rst0Marker = 0xd0; // ReSTart (0). + private const int rst7Marker = 0xd7; // ReSTart (7). + private const int soiMarker = 0xd8; // Start Of Image. + private const int eoiMarker = 0xd9; // End Of Image. + private const int sosMarker = 0xda; // Start Of Scan. + private const int dqtMarker = 0xdb; // Define Quantization Table. + private const int driMarker = 0xdd; // Define Restart Interval. + private const int comMarker = 0xfe; // COMment. + // "APPlication specific" markers aren't part of the JPEG spec per se, // but in practice, their use is described at // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html private const int app0Marker = 0xe0; + private const int app14Marker = 0xee; + private const int app15Marker = 0xef; // bitCount counts the number of bits needed to hold an integer. - private readonly byte[] bitCount = { - 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, }; + private readonly byte[] bitCount = + { + 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, + }; // unzig maps from the zig-zag ordering to the natural ordering. For example, // unzig[3] is the column and row of the fourth element in zig-zag order. The // value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). - private static readonly int[] unzig = new int[] { - 0, 1, 8, 16, 9, 2, 3, 10, - 17, 24, 32, 25, 18, 11, 4, 5, - 12, 19, 26, 33, 40, 48, 41, 34, - 27, 20, 13, 6, 7, 14, 21, 28, - 35, 42, 49, 56, 57, 50, 43, 36, - 29, 22, 15, 23, 30, 37, 44, 51, - 58, 59, 52, 45, 38, 31, 39, 46, - 53, 60, 61, 54, 47, 55, 62, 63, - }; + private static readonly int[] unzig = new[] + { + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, + 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, + 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, + 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, + }; private const int nQuantIndex = 2; + private const int nHuffIndex = 4; private enum quantIndex { - quantIndexLuminance = 0, - quantIndexChrominance = 1, + quantIndexLuminance = 0, + + quantIndexChrominance = 1, } private enum huffIndex { - huffIndexLuminanceDC = 0, - huffIndexLuminanceAC = 1, - huffIndexChrominanceDC = 2, - huffIndexChrominanceAC = 3, + huffIndexLuminanceDC = 0, + + huffIndexLuminanceAC = 1, + + huffIndexChrominanceDC = 2, + + huffIndexChrominanceAC = 3, } // unscaledQuant are the unscaled quantization tables in zig-zag order. Each // encoder copies and scales the tables according to its quality parameter. // The values are derived from section K.1 after converting from natural to // zig-zag order. - private byte[,] unscaledQuant = new byte[,] { - // Luminance. - { - 16, 11, 12, 14, 12, 10, 16, 14, - 13, 14, 18, 17, 16, 19, 24, 40, - 26, 24, 22, 22, 24, 49, 35, 37, - 29, 40, 58, 51, 61, 60, 57, 51, - 56, 55, 64, 72, 92, 78, 64, 68, - 87, 69, 55, 56, 80, 109, 81, 87, - 95, 98, 103, 104, 103, 62, 77, 113, - 121, 112, 100, 120, 92, 101, 103, 99, - }, - // Chrominance. - { - 17, 18, 18, 24, 21, 24, 47, 26, - 26, 47, 99, 66, 56, 66, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, - }, - }; + private byte[,] unscaledQuant = new byte[,] + { + { + // Luminance. + 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, 40, + 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, 57, 51, + 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, 109, 81, + 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, 100, 120, 92, + 101, 103, 99, + }, + { + // Chrominance. + 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + }, + }; private class huffmanSpec { - public huffmanSpec(byte[] c, byte[] v) { count = c; values = v; } + public huffmanSpec(byte[] c, byte[] v) + { + this.count = c; + this.values = v; + } + public byte[] count; + public byte[] values; } // theHuffmanSpec is the Huffman encoding specifications. // This encoder uses the same Huffman encoding for all images. - private huffmanSpec[] theHuffmanSpec = new huffmanSpec[] { + private huffmanSpec[] theHuffmanSpec = new[] + { // Luminance DC. new huffmanSpec( - new byte[] { 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }), + new byte[] + { + 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 + }, + new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }), new huffmanSpec( - new byte[] { 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125 }, new byte[] { - 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, - 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, - 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, - 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, - 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, - 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, - 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, - 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, - 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, - 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, - 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, - 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, - 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, - 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, - 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, - 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, - 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, - 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, - 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, - 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, - 0xf9, 0xfa}), + 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125 + }, + new byte[] + { + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, + 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, + 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, + 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, + 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, + 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, + 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa + }), new huffmanSpec( - new byte[] { 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 }, - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }), + new byte[] + { + 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 + }, + new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }), // Chrominance AC. new huffmanSpec( - new byte[] { 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119 }, new byte[] { - 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, - 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, - 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, - 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, - 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, - 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, - 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, - 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, - 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, - 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, - 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, - 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, - 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, - 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, - 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, - 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, - 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, - 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, - 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, - 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, - 0xf9, 0xfa, - }) + 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119 + }, + new byte[] + { + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, + 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, + 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, + 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, + 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, + 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, + 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, + 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, + 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, + 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, + 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, + }) }; // huffmanLUT is a compiled look-up table representation of a huffmanSpec. @@ -194,11 +214,10 @@ namespace ImageProcessorCore.Formats foreach (var v in s.values) { - if (v > maxValue) - maxValue = v; + if (v > maxValue) maxValue = v; } - values = new uint[maxValue + 1]; + this.values = new uint[maxValue + 1]; int code = 0; int k = 0; @@ -208,10 +227,11 @@ namespace ImageProcessorCore.Formats int nBits = (i + 1) << 24; for (int j = 0; j < s.count[i]; j++) { - values[s.values[k]] = (uint)(nBits | code); + this.values[s.values[k]] = (uint)(nBits | code); code++; k++; } + code <<= 1; } } @@ -220,21 +240,28 @@ namespace ImageProcessorCore.Formats // w is the writer to write to. err is the first error encountered during // writing. All attempted writes after the first error become no-ops. private Stream outputStream; + // buf is a scratch buffer. private byte[] buf = new byte[16]; + // bits and nBits are accumulated bits to write to w. - private uint bits, nBits; + private uint bits; + + private uint nBits; + // quant is the scaled quantization tables, in zig-zag order. - private byte[][] quant = new byte[nQuantIndex][];//[Block.blockSize]; + private byte[][] quant = new byte[nQuantIndex][]; // [Block.blockSize]; + // theHuffmanLUT are compiled representations of theHuffmanSpec. private huffmanLUT[] theHuffmanLUT = new huffmanLUT[4]; + private JpegSubsample subsample; private void writeByte(byte b) { var data = new byte[1]; data[0] = b; - outputStream.Write(data, 0, 1); + this.outputStream.Write(data, 0, 1); } // emit emits the least significant nBits bits of bits to the bit-stream. @@ -247,12 +274,12 @@ namespace ImageProcessorCore.Formats while (nBits >= 8) { byte b = (byte)(bits >> 24); - writeByte(b); - if (b == 0xff) - writeByte(0x00); + this.writeByte(b); + if (b == 0xff) this.writeByte(0x00); bits <<= 8; nBits -= 8; } + this.bits = bits; this.nBits = nBits; } @@ -260,8 +287,8 @@ namespace ImageProcessorCore.Formats // emitHuff emits the given value with the given Huffman encoder. private void emitHuff(huffIndex h, int v) { - uint x = theHuffmanLUT[(int)h].values[v]; - emit(x & ((1 << 24) - 1), x >> 24); + uint x = this.theHuffmanLUT[(int)h].values[v]; + this.emit(x & ((1 << 24) - 1), x >> 24); } // emitHuffRLE emits a run of runLength copies of value encoded with the given @@ -275,46 +302,45 @@ namespace ImageProcessorCore.Formats a = -v; b = v - 1; } + uint nBits = 0; - if (a < 0x100) - nBits = bitCount[a]; - else - nBits = 8 + (uint)bitCount[a >> 8]; + if (a < 0x100) nBits = this.bitCount[a]; + else nBits = 8 + (uint)this.bitCount[a >> 8]; - emitHuff(h, (int)((uint)(runLength << 4) | nBits)); - if (nBits > 0) emit((uint)b & (uint)((1 << ((int)nBits)) - 1), nBits); + this.emitHuff(h, (int)((uint)(runLength << 4) | nBits)); + if (nBits > 0) this.emit((uint)b & (uint)((1 << ((int)nBits)) - 1), nBits); } // writeMarkerHeader writes the header for a marker with the given length. private void writeMarkerHeader(byte marker, int markerlen) { - buf[0] = 0xff; - buf[1] = marker; - buf[2] = (byte)(markerlen >> 8); - buf[3] = (byte)(markerlen & 0xff); - outputStream.Write(buf, 0, 4); + this.buf[0] = 0xff; + this.buf[1] = marker; + this.buf[2] = (byte)(markerlen >> 8); + this.buf[3] = (byte)(markerlen & 0xff); + this.outputStream.Write(this.buf, 0, 4); } // writeDQT writes the Define Quantization Table marker. private void writeDQT() { int markerlen = 2 + nQuantIndex * (1 + Block.BlockSize); - writeMarkerHeader(dqtMarker, markerlen); + this.writeMarkerHeader(dqtMarker, markerlen); for (int i = 0; i < nQuantIndex; i++) { - writeByte((byte)i); - outputStream.Write(quant[i], 0, quant[i].Length); + this.writeByte((byte)i); + this.outputStream.Write(this.quant[i], 0, this.quant[i].Length); } } // writeSOF0 writes the Start Of Frame (Baseline) marker. private void writeSOF0(int wid, int hei, int nComponent) { - //"default" to 4:2:0 - byte[] subsamples = new byte[] { 0x22, 0x11, 0x11 }; - byte[] chroma = new byte[] { 0x00, 0x01, 0x01 }; + // "default" to 4:2:0 + byte[] subsamples = { 0x22, 0x11, 0x11 }; + byte[] chroma = { 0x00, 0x01, 0x01 }; - switch (subsample) + switch (this.subsample) { case JpegSubsample.Ratio444: subsamples = new byte[] { 0x11, 0x11, 0x11 }; @@ -325,31 +351,34 @@ namespace ImageProcessorCore.Formats } int markerlen = 8 + 3 * nComponent; - writeMarkerHeader(sof0Marker, markerlen); - buf[0] = 8; // 8-bit color. - buf[1] = (byte)(hei >> 8); - buf[2] = (byte)(hei & 0xff); - buf[3] = (byte)(wid >> 8); - buf[4] = (byte)(wid & 0xff); - buf[5] = (byte)(nComponent); + this.writeMarkerHeader(sof0Marker, markerlen); + this.buf[0] = 8; // 8-bit color. + this.buf[1] = (byte)(hei >> 8); + this.buf[2] = (byte)(hei & 0xff); + this.buf[3] = (byte)(wid >> 8); + this.buf[4] = (byte)(wid & 0xff); + this.buf[5] = (byte)nComponent; if (nComponent == 1) { - buf[6] = 1; + this.buf[6] = 1; + // No subsampling for grayscale image. - buf[7] = 0x11; - buf[8] = 0x00; + this.buf[7] = 0x11; + this.buf[8] = 0x00; } else { for (int i = 0; i < nComponent; i++) { - buf[3 * i + 6] = (byte)(i + 1); + this.buf[3 * i + 6] = (byte)(i + 1); + // We use 4:2:0 chroma subsampling. - buf[3 * i + 7] = subsamples[i]; - buf[3 * i + 8] = chroma[i]; + this.buf[3 * i + 7] = subsamples[i]; + this.buf[3 * i + 8] = chroma[i]; } } - outputStream.Write(buf, 0, 3 * (nComponent - 1) + 9); + + this.outputStream.Write(this.buf, 0, 3 * (nComponent - 1) + 9); } // writeDHT writes the Define Huffman Table marker. @@ -357,12 +386,12 @@ namespace ImageProcessorCore.Formats { byte[] headers = new byte[] { 0x00, 0x10, 0x01, 0x11 }; int markerlen = 2; - huffmanSpec[] specs = theHuffmanSpec; + huffmanSpec[] specs = this.theHuffmanSpec; if (nComponent == 1) { // Drop the Chrominance tables. - specs = new huffmanSpec[] { theHuffmanSpec[0], theHuffmanSpec[1] }; + specs = new[] { this.theHuffmanSpec[0], this.theHuffmanSpec[1] }; } foreach (var s in specs) @@ -370,14 +399,14 @@ namespace ImageProcessorCore.Formats markerlen += 1 + 16 + s.values.Length; } - writeMarkerHeader(dhtMarker, markerlen); + this.writeMarkerHeader(dhtMarker, markerlen); for (int i = 0; i < specs.Length; i++) { var s = specs[i]; - writeByte(headers[i]); - outputStream.Write(s.count, 0, s.count.Length); - outputStream.Write(s.values, 0, s.values.Length); + this.writeByte(headers[i]); + this.outputStream.Write(s.count, 0, s.count.Length); + this.outputStream.Write(s.values, 0, s.values.Length); } } @@ -389,8 +418,8 @@ namespace ImageProcessorCore.Formats FDCT.Transform(b); // Emit the DC delta. - int dc = div(b[0], 8 * quant[(int)q][0]); - emitHuffRLE((huffIndex)(2 * (int)q + 0), 0, dc - prevDC); + int dc = div(b[0], 8 * this.quant[(int)q][0]); + this.emitHuffRLE((huffIndex)(2 * (int)q + 0), 0, dc - prevDC); // Emit the AC components. var h = (huffIndex)(2 * (int)q + 1); @@ -398,7 +427,7 @@ namespace ImageProcessorCore.Formats for (int zig = 1; zig < Block.BlockSize; zig++) { - int ac = div(b[unzig[zig]], 8 * quant[(int)q][zig]); + int ac = div(b[unzig[zig]], 8 * this.quant[(int)q][zig]); if (ac == 0) { @@ -408,16 +437,16 @@ namespace ImageProcessorCore.Formats { while (runLength > 15) { - emitHuff(h, 0xf0); + this.emitHuff(h, 0xf0); runLength -= 16; } - emitHuffRLE(h, runLength, ac); + this.emitHuffRLE(h, runLength, ac); runLength = 0; } } - if (runLength > 0) - emitHuff(h, 0x00); + + if (runLength > 0) this.emitHuff(h, 0x00); return dc; } @@ -460,48 +489,46 @@ namespace ImageProcessorCore.Formats } // sosHeaderY is the SOS marker "\xff\xda" followed by 8 bytes: - // - the marker length "\x00\x08", - // - the number of components "\x01", - // - component 1 uses DC table 0 and AC table 0 "\x01\x00", - // - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for - // sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) - // should be 0x00, 0x3f, 0x00<<4 | 0x00. - private readonly byte[] sosHeaderY = new byte[] { - 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, - }; + // - the marker length "\x00\x08", + // - the number of components "\x01", + // - component 1 uses DC table 0 and AC table 0 "\x01\x00", + // - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for + // sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) + // should be 0x00, 0x3f, 0x00<<4 | 0x00. + private readonly byte[] sosHeaderY = new byte[] { 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, }; // sosHeaderYCbCr is the SOS marker "\xff\xda" followed by 12 bytes: - // - the marker length "\x00\x0c", - // - the number of components "\x03", - // - component 1 uses DC table 0 and AC table 0 "\x01\x00", - // - component 2 uses DC table 1 and AC table 1 "\x02\x11", - // - component 3 uses DC table 1 and AC table 1 "\x03\x11", - // - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for - // sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) - // should be 0x00, 0x3f, 0x00<<4 | 0x00. - private readonly byte[] sosHeaderYCbCr = new byte[] { - 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, - 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, + // - the marker length "\x00\x0c", + // - the number of components "\x03", + // - component 1 uses DC table 0 and AC table 0 "\x01\x00", + // - component 2 uses DC table 1 and AC table 1 "\x02\x11", + // - component 3 uses DC table 1 and AC table 1 "\x03\x11", + // - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for + // sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) + // should be 0x00, 0x3f, 0x00<<4 | 0x00. + private readonly byte[] sosHeaderYCbCr = new byte[] + { + 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, + 0x00, 0x3f, 0x00, }; - // writeSOS writes the StartOfScan marker. private void writeSOS(PixelAccessor pixels) { - outputStream.Write(sosHeaderYCbCr, 0, sosHeaderYCbCr.Length); + this.outputStream.Write(this.sosHeaderYCbCr, 0, this.sosHeaderYCbCr.Length); - switch (subsample) + switch (this.subsample) { case JpegSubsample.Ratio444: - encode444(pixels); + this.encode444(pixels); break; case JpegSubsample.Ratio420: - encode420(pixels); + this.encode420(pixels); break; } // Pad the last byte with 1's. - emit(0x7f, 7); + this.emit(0x7f, 7); } private void encode444(PixelAccessor pixels) @@ -515,10 +542,10 @@ namespace ImageProcessorCore.Formats { for (int x = 0; x < pixels.Width; x += 8) { - toYCbCr(pixels, x, y, b, cb, cr); - prevDCY = writeBlock(b, (quantIndex)0, prevDCY); - prevDCCb = writeBlock(cb, (quantIndex)1, prevDCCb); - prevDCCr = writeBlock(cr, (quantIndex)1, prevDCCr); + this.toYCbCr(pixels, x, y, b, cb, cr); + prevDCY = this.writeBlock(b, (quantIndex)0, prevDCY); + prevDCCb = this.writeBlock(cb, (quantIndex)1, prevDCCb); + prevDCCr = this.writeBlock(cr, (quantIndex)1, prevDCCr); } } } @@ -542,37 +569,43 @@ namespace ImageProcessorCore.Formats int xOff = (i & 1) * 8; int yOff = (i & 2) * 4; - toYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); - prevDCY = writeBlock(b, (quantIndex)0, prevDCY); + this.toYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); + prevDCY = this.writeBlock(b, (quantIndex)0, prevDCY); } - scale_16x16_8x8(b, cb); - prevDCCb = writeBlock(b, (quantIndex)1, prevDCCb); - scale_16x16_8x8(b, cr); - prevDCCr = writeBlock(b, (quantIndex)1, prevDCCr); + + this.scale_16x16_8x8(b, cb); + prevDCCb = this.writeBlock(b, (quantIndex)1, prevDCCb); + this.scale_16x16_8x8(b, cr); + prevDCCr = this.writeBlock(b, (quantIndex)1, prevDCCr); } } } // Encode writes the Image m to w in JPEG 4:2:0 baseline format with the given // options. Default parameters are used if a nil *Options is passed. - public void Encode(Stream stream, ImageBase image, int quality, JpegSubsample subsample) + public void Encode(Stream stream, ImageBase image, int quality, JpegSubsample sample) { - this.outputStream = stream; - this.subsample = subsample; + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); - for (int i = 0; i < theHuffmanSpec.Length; i++) + ushort max = JpegConstants.MaxLength; + if (image.Width >= max || image.Height >= max) { - theHuffmanLUT[i] = new huffmanLUT(theHuffmanSpec[i]); + throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}."); } - for (int i = 0; i < nQuantIndex; i++) + this.outputStream = stream; + this.subsample = sample; + + // TODO: This should be static should it not? + for (int i = 0; i < this.theHuffmanSpec.Length; i++) { - quant[i] = new byte[Block.BlockSize]; + this.theHuffmanLUT[i] = new huffmanLUT(this.theHuffmanSpec[i]); } - if (image.Width >= (1 << 16) || image.Height >= (1 << 16)) + for (int i = 0; i < nQuantIndex; i++) { - throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}."); + this.quant[i] = new byte[Block.BlockSize]; } if (quality < 1) quality = 1; @@ -594,11 +627,11 @@ namespace ImageProcessorCore.Formats { for (int j = 0; j < Block.BlockSize; j++) { - int x = unscaledQuant[i, j]; + int x = this.unscaledQuant[i, j]; x = (x * scale + 50) / 100; if (x < 1) x = 1; if (x > 255) x = 255; - quant[i][j] = (byte)x; + this.quant[i][j] = (byte)x; } } @@ -606,39 +639,37 @@ namespace ImageProcessorCore.Formats int nComponent = 3; // Write the Start Of Image marker. - buf[0] = 0xff; - buf[1] = 0xd8; - stream.Write(buf, 0, 2); + this.buf[0] = 0xff; + this.buf[1] = 0xd8; + stream.Write(this.buf, 0, 2); // Write the quantization tables. - writeDQT(); + this.writeDQT(); // Write the image dimensions. - writeSOF0(image.Width, image.Height, nComponent); + this.writeSOF0(image.Width, image.Height, nComponent); // Write the Huffman tables. - writeDHT(nComponent); + this.writeDHT(nComponent); // Write the image data. using (PixelAccessor pixels = image.Lock()) { - writeSOS(pixels); + this.writeSOS(pixels); } // Write the End Of Image marker. - buf[0] = 0xff; - buf[1] = 0xd9; - stream.Write(buf, 0, 2); + this.buf[0] = 0xff; + this.buf[1] = 0xd9; + stream.Write(this.buf, 0, 2); stream.Flush(); } // div returns a/b rounded to the nearest integer, instead of rounded to zero. private static int div(int a, int b) { - if (a >= 0) - return (a + (b >> 1)) / b; - else - return -((-a + (b >> 1)) / b); + if (a >= 0) return (a + (b >> 1)) / b; + else return -((-a + (b >> 1)) / b); } } } From 96ed7e7c31b3a588b6071d876828b7cc72a8d0aa Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 20 Jul 2016 18:22:30 +1000 Subject: [PATCH 4/8] Use hex Former-commit-id: ac8bf5b9037a86dbd4ee9d99957b6898a8fc11c0 Former-commit-id: b3eab977b14fa51d583340c859838d587eefb086 Former-commit-id: 63c923257bd9340b3a27b92899a1818ea79c9717 --- src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs b/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs index 27514d667..91dab75c6 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs @@ -18,32 +18,32 @@ namespace ImageProcessorCore.Formats /// /// Represents high detail chroma horizontal subsampling. /// - public static readonly byte[] ChromaFourFourFourHorizontal = { 1, 1, 1 }; + public static readonly byte[] ChromaFourFourFourHorizontal = { 0x1, 0x1, 0x1 }; /// /// Represents high detail chroma vertical subsampling. /// - public static readonly byte[] ChromaFourFourFourVertical = { 1, 1, 1 }; + public static readonly byte[] ChromaFourFourFourVertical = { 0x1, 0x1, 0x1 }; /// /// Represents medium detail chroma horizontal subsampling. /// - public static readonly byte[] ChromaFourTwoTwoHorizontal = { 2, 1, 1 }; + public static readonly byte[] ChromaFourTwoTwoHorizontal = { 0x2, 0x1, 0x1 }; /// /// Represents medium detail chroma vertical subsampling. /// - public static readonly byte[] ChromaFourTwoTwoVertical = { 1, 1, 1 }; + public static readonly byte[] ChromaFourTwoTwoVertical = { 0x1, 0x1, 0x1 }; /// /// Represents low detail chroma horizontal subsampling. /// - public static readonly byte[] ChromaFourTwoZeroHorizontal = { 2, 1, 1 }; + public static readonly byte[] ChromaFourTwoZeroHorizontal = { 0x2, 0x1, 0x1 }; /// /// Represents low detail chroma vertical subsampling. /// - public static readonly byte[] ChromaFourTwoZeroVertical = { 2, 1, 1 }; + public static readonly byte[] ChromaFourTwoZeroVertical = { 0x2, 0x1, 0x1 }; /// /// Describes component ids for start of frame components. From 0ad8ca517cba28257b1a3226bbdc614f4cf03f4e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 25 Jul 2016 14:11:04 +1000 Subject: [PATCH 5/8] More jpg cleanup Former-commit-id: 404825d34ba9f39abb1cac223330a3075c9498df Former-commit-id: 56c5a78950278bcbd4af5b1e992e38ed1ddd3a9b Former-commit-id: f75cdaa144f70e3c7ed8e7e750977dfa92866a26 --- .../Formats/Jpg/JpegConstants.cs | 23 +- .../Formats/Jpg/JpegEncoderCore.cs | 624 +++++++++--------- 2 files changed, 346 insertions(+), 301 deletions(-) diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs b/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs index 91dab75c6..22c6a8c9b 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs @@ -18,32 +18,32 @@ namespace ImageProcessorCore.Formats /// /// Represents high detail chroma horizontal subsampling. /// - public static readonly byte[] ChromaFourFourFourHorizontal = { 0x1, 0x1, 0x1 }; + public static readonly byte[] ChromaFourFourFourHorizontal = { 0x11, 0x11, 0x11 }; /// /// Represents high detail chroma vertical subsampling. /// - public static readonly byte[] ChromaFourFourFourVertical = { 0x1, 0x1, 0x1 }; + public static readonly byte[] ChromaFourFourFourVertical = { 0x11, 0x11, 0x11 }; /// /// Represents medium detail chroma horizontal subsampling. /// - public static readonly byte[] ChromaFourTwoTwoHorizontal = { 0x2, 0x1, 0x1 }; + public static readonly byte[] ChromaFourTwoTwoHorizontal = { 0x22, 0x11, 0x11 }; /// /// Represents medium detail chroma vertical subsampling. /// - public static readonly byte[] ChromaFourTwoTwoVertical = { 0x1, 0x1, 0x1 }; + public static readonly byte[] ChromaFourTwoTwoVertical = { 0x11, 0x11, 0x11 }; /// /// Represents low detail chroma horizontal subsampling. /// - public static readonly byte[] ChromaFourTwoZeroHorizontal = { 0x2, 0x1, 0x1 }; + public static readonly byte[] ChromaFourTwoZeroHorizontal = { 0x22, 0x11, 0x11 }; /// /// Represents low detail chroma vertical subsampling. /// - public static readonly byte[] ChromaFourTwoZeroVertical = { 0x2, 0x1, 0x1 }; + public static readonly byte[] ChromaFourTwoZeroVertical = { 0x22, 0x11, 0x11 }; /// /// Describes component ids for start of frame components. @@ -100,6 +100,15 @@ namespace ImageProcessorCore.Formats /// public const byte SOF0 = 0xc0; + /// + /// Start Of Frame (Extended Sequential DCT) + /// + /// Indicates that this is a progressive DCT-based JPEG, and specifies the width, height, number of components, + /// and component subsampling (e.g., 4:2:0). + /// + /// + public const byte SOF1 = 0xc1; + /// /// Start Of Frame (progressive DCT) /// @@ -107,7 +116,7 @@ namespace ImageProcessorCore.Formats /// and component subsampling (e.g., 4:2:0). /// /// - public const byte SOF2 = 0xc0; + public const byte SOF2 = 0xc2; /// /// Define Huffman Table(s) diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs index 3274907d5..d2a7e35e0 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs @@ -9,30 +9,6 @@ namespace ImageProcessorCore.Formats internal class JpegEncoderCore { - private const int sof0Marker = 0xc0; // Start Of Frame (Baseline). - - private const int sof1Marker = 0xc1; // Start Of Frame (Extended Sequential). - - private const int sof2Marker = 0xc2; // Start Of Frame (Progressive). - - private const int dhtMarker = 0xc4; // Define Huffman Table. - - private const int rst0Marker = 0xd0; // ReSTart (0). - - private const int rst7Marker = 0xd7; // ReSTart (7). - - private const int soiMarker = 0xd8; // Start Of Image. - - private const int eoiMarker = 0xd9; // End Of Image. - - private const int sosMarker = 0xda; // Start Of Scan. - - private const int dqtMarker = 0xdb; // Define Quantization Table. - - private const int driMarker = 0xdd; // Define Restart Interval. - - private const int comMarker = 0xfe; // COMment. - // "APPlication specific" markers aren't part of the JPEG spec per se, // but in practice, their use is described at // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html @@ -45,29 +21,29 @@ namespace ImageProcessorCore.Formats // bitCount counts the number of bits needed to hold an integer. private readonly byte[] bitCount = { - 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, + 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, }; // unzig maps from the zig-zag ordering to the natural ordering. For example, // unzig[3] is the column and row of the fourth element in zig-zag order. The // value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). private static readonly int[] unzig = new[] - { - 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, - 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, - 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, - 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, - }; + { + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, + 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, + 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, + 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, + }; private const int nQuantIndex = 2; @@ -75,20 +51,20 @@ namespace ImageProcessorCore.Formats private enum quantIndex { - quantIndexLuminance = 0, + quantIndexLuminance = 0, - quantIndexChrominance = 1, + quantIndexChrominance = 1, } private enum huffIndex { - huffIndexLuminanceDC = 0, + huffIndexLuminanceDC = 0, - huffIndexLuminanceAC = 1, + huffIndexLuminanceAC = 1, - huffIndexChrominanceDC = 2, + huffIndexChrominanceDC = 2, - huffIndexChrominanceAC = 3, + huffIndexChrominanceAC = 3, } // unscaledQuant are the unscaled quantization tables in zig-zag order. Each @@ -99,19 +75,19 @@ namespace ImageProcessorCore.Formats { { // Luminance. - 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, 40, - 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, 57, 51, - 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, 109, 81, - 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, 100, 120, 92, - 101, 103, 99, - }, + 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, 40, + 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, 57, 51, + 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, 109, 81, + 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, 100, 120, 92, + 101, 103, 99, + }, { // Chrominance. - 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - }, + 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + }, }; private class huffmanSpec @@ -135,68 +111,68 @@ namespace ImageProcessorCore.Formats new huffmanSpec( new byte[] { - 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 - }, - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }), + 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 + }, + new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }), new huffmanSpec( new byte[] { - 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125 - }, + 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125 + }, new byte[] { - 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, - 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, - 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, - 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, - 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, - 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, - 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, - 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, - 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, - 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, - 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, - 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, - 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, - 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, - 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, - 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, + 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, + 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, + 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, + 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, + 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, + 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa - }), + }), new huffmanSpec( new byte[] { - 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 - }, + 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 + }, new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }), // Chrominance AC. new huffmanSpec( new byte[] { - 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119 - }, + 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119 + }, new byte[] { - 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, - 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, - 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, - 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, - 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, - 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, - 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, - 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, - 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, - 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, - 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, - 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, - 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, - 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, - 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, - 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, - 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, - 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, + 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, + 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, + 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, + 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, + 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, + 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, + 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, + 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, + 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, + 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, }) }; @@ -242,7 +218,7 @@ namespace ImageProcessorCore.Formats private Stream outputStream; // buf is a scratch buffer. - private byte[] buf = new byte[16]; + private byte[] buffer = new byte[16]; // bits and nBits are accumulated bits to write to w. private uint bits; @@ -311,104 +287,6 @@ namespace ImageProcessorCore.Formats if (nBits > 0) this.emit((uint)b & (uint)((1 << ((int)nBits)) - 1), nBits); } - // writeMarkerHeader writes the header for a marker with the given length. - private void writeMarkerHeader(byte marker, int markerlen) - { - this.buf[0] = 0xff; - this.buf[1] = marker; - this.buf[2] = (byte)(markerlen >> 8); - this.buf[3] = (byte)(markerlen & 0xff); - this.outputStream.Write(this.buf, 0, 4); - } - - // writeDQT writes the Define Quantization Table marker. - private void writeDQT() - { - int markerlen = 2 + nQuantIndex * (1 + Block.BlockSize); - this.writeMarkerHeader(dqtMarker, markerlen); - for (int i = 0; i < nQuantIndex; i++) - { - this.writeByte((byte)i); - this.outputStream.Write(this.quant[i], 0, this.quant[i].Length); - } - } - - // writeSOF0 writes the Start Of Frame (Baseline) marker. - private void writeSOF0(int wid, int hei, int nComponent) - { - // "default" to 4:2:0 - byte[] subsamples = { 0x22, 0x11, 0x11 }; - byte[] chroma = { 0x00, 0x01, 0x01 }; - - switch (this.subsample) - { - case JpegSubsample.Ratio444: - subsamples = new byte[] { 0x11, 0x11, 0x11 }; - break; - case JpegSubsample.Ratio420: - subsamples = new byte[] { 0x22, 0x11, 0x11 }; - break; - } - - int markerlen = 8 + 3 * nComponent; - this.writeMarkerHeader(sof0Marker, markerlen); - this.buf[0] = 8; // 8-bit color. - this.buf[1] = (byte)(hei >> 8); - this.buf[2] = (byte)(hei & 0xff); - this.buf[3] = (byte)(wid >> 8); - this.buf[4] = (byte)(wid & 0xff); - this.buf[5] = (byte)nComponent; - if (nComponent == 1) - { - this.buf[6] = 1; - - // No subsampling for grayscale image. - this.buf[7] = 0x11; - this.buf[8] = 0x00; - } - else - { - for (int i = 0; i < nComponent; i++) - { - this.buf[3 * i + 6] = (byte)(i + 1); - - // We use 4:2:0 chroma subsampling. - this.buf[3 * i + 7] = subsamples[i]; - this.buf[3 * i + 8] = chroma[i]; - } - } - - this.outputStream.Write(this.buf, 0, 3 * (nComponent - 1) + 9); - } - - // writeDHT writes the Define Huffman Table marker. - private void writeDHT(int nComponent) - { - byte[] headers = new byte[] { 0x00, 0x10, 0x01, 0x11 }; - int markerlen = 2; - huffmanSpec[] specs = this.theHuffmanSpec; - - if (nComponent == 1) - { - // Drop the Chrominance tables. - specs = new[] { this.theHuffmanSpec[0], this.theHuffmanSpec[1] }; - } - - foreach (var s in specs) - { - markerlen += 1 + 16 + s.values.Length; - } - - this.writeMarkerHeader(dhtMarker, markerlen); - for (int i = 0; i < specs.Length; i++) - { - var s = specs[i]; - - this.writeByte(headers[i]); - this.outputStream.Write(s.count, 0, s.count.Length); - this.outputStream.Write(s.values, 0, s.values.Length); - } - } // writeBlock writes a block of pixel data using the given quantization table, // returning the post-quantized DC value of the DCT-transformed block. b is in @@ -418,7 +296,7 @@ namespace ImageProcessorCore.Formats FDCT.Transform(b); // Emit the DC delta. - int dc = div(b[0], 8 * this.quant[(int)q][0]); + int dc = Round(b[0], 8 * this.quant[(int)q][0]); this.emitHuffRLE((huffIndex)(2 * (int)q + 0), 0, dc - prevDC); // Emit the AC components. @@ -427,7 +305,7 @@ namespace ImageProcessorCore.Formats for (int zig = 1; zig < Block.BlockSize; zig++) { - int ac = div(b[unzig[zig]], 8 * this.quant[(int)q][zig]); + int ac = Round(b[unzig[zig]], 8 * this.quant[(int)q][zig]); if (ac == 0) { @@ -488,16 +366,26 @@ namespace ImageProcessorCore.Formats } } - // sosHeaderY is the SOS marker "\xff\xda" followed by 8 bytes: + // The SOS marker "\xff\xda" followed by 8 bytes: // - the marker length "\x00\x08", // - the number of components "\x01", // - component 1 uses DC table 0 and AC table 0 "\x01\x00", // - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for // sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) // should be 0x00, 0x3f, 0x00<<4 | 0x00. - private readonly byte[] sosHeaderY = new byte[] { 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, }; + private readonly byte[] SOSHeaderY = + { + JpegConstants.Markers.XFF, JpegConstants.Markers.SOS, + 0x00, 0x08, // Length (high byte, low byte), must be 6 + 2 * (number of components in scan) + 0x01, // Number of components in a scan, 1 + 0x01, // Component Id Y + 0x00, // DC/AC Huffman table + 0x00, // Ss - Start of spectral selection. + 0x3f, // Se - End of spectral selection. + 0x00 // Ah + Ah (Successive approximation bit position high + low) + }; - // sosHeaderYCbCr is the SOS marker "\xff\xda" followed by 12 bytes: + // The SOS marker "\xff\xda" followed by 12 bytes: // - the marker length "\x00\x0c", // - the number of components "\x03", // - component 1 uses DC table 0 and AC table 0 "\x01\x00", @@ -506,81 +394,22 @@ namespace ImageProcessorCore.Formats // - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for // sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) // should be 0x00, 0x3f, 0x00<<4 | 0x00. - private readonly byte[] sosHeaderYCbCr = new byte[] + private readonly byte[] SOSHeaderYCbCr = { - 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, - 0x00, 0x3f, 0x00, + JpegConstants.Markers.XFF, JpegConstants.Markers.SOS, + 0x00, 0x0c, // Length (high byte, low byte), must be 6 + 2 * (number of components in scan) + 0x03, // Number of components in a scan, 3 + 0x01, // Component Id Y + 0x00, // DC/AC Huffman table + 0x02, // Component Id Cb + 0x11, // DC/AC Huffman table + 0x03, // Component Id Cr + 0x11, // DC/AC Huffman table + 0x00, // Ss - Start of spectral selection. + 0x3f, // Se - End of spectral selection. + 0x00 // Ah + Ah (Successive approximation bit position high + low) }; - // writeSOS writes the StartOfScan marker. - private void writeSOS(PixelAccessor pixels) - { - this.outputStream.Write(this.sosHeaderYCbCr, 0, this.sosHeaderYCbCr.Length); - - switch (this.subsample) - { - case JpegSubsample.Ratio444: - this.encode444(pixels); - break; - case JpegSubsample.Ratio420: - this.encode420(pixels); - break; - } - - // Pad the last byte with 1's. - this.emit(0x7f, 7); - } - - private void encode444(PixelAccessor pixels) - { - Block b = new Block(); - Block cb = new Block(); - Block cr = new Block(); - int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - - for (int y = 0; y < pixels.Height; y += 8) - { - for (int x = 0; x < pixels.Width; x += 8) - { - this.toYCbCr(pixels, x, y, b, cb, cr); - prevDCY = this.writeBlock(b, (quantIndex)0, prevDCY); - prevDCCb = this.writeBlock(cb, (quantIndex)1, prevDCCb); - prevDCCr = this.writeBlock(cr, (quantIndex)1, prevDCCr); - } - } - } - - private void encode420(PixelAccessor pixels) - { - Block b = new Block(); - Block[] cb = new Block[4]; - Block[] cr = new Block[4]; - int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; - - for (int i = 0; i < 4; i++) cb[i] = new Block(); - for (int i = 0; i < 4; i++) cr[i] = new Block(); - - for (int y = 0; y < pixels.Height; y += 16) - { - for (int x = 0; x < pixels.Width; x += 16) - { - for (int i = 0; i < 4; i++) - { - int xOff = (i & 1) * 8; - int yOff = (i & 2) * 4; - - this.toYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); - prevDCY = this.writeBlock(b, (quantIndex)0, prevDCY); - } - - this.scale_16x16_8x8(b, cb); - prevDCCb = this.writeBlock(b, (quantIndex)1, prevDCCb); - this.scale_16x16_8x8(b, cr); - prevDCCr = this.writeBlock(b, (quantIndex)1, prevDCCr); - } - } - } - // Encode writes the Image m to w in JPEG 4:2:0 baseline format with the given // options. Default parameters are used if a nil *Options is passed. public void Encode(Stream stream, ImageBase image, int quality, JpegSubsample sample) @@ -636,40 +465,247 @@ namespace ImageProcessorCore.Formats } // Compute number of components based on input image type. - int nComponent = 3; + int componentCount = 3; // Write the Start Of Image marker. - this.buf[0] = 0xff; - this.buf[1] = 0xd8; - stream.Write(this.buf, 0, 2); + // TODO: JFIF header etc. + this.buffer[0] = 0xff; + this.buffer[1] = 0xd8; + stream.Write(this.buffer, 0, 2); // Write the quantization tables. - this.writeDQT(); + this.WriteDQT(); // Write the image dimensions. - this.writeSOF0(image.Width, image.Height, nComponent); + this.WriteSOF0(image.Width, image.Height, componentCount); // Write the Huffman tables. - this.writeDHT(nComponent); + this.WriteDHT(componentCount); // Write the image data. using (PixelAccessor pixels = image.Lock()) { - this.writeSOS(pixels); + this.WriteSOS(pixels); } // Write the End Of Image marker. - this.buf[0] = 0xff; - this.buf[1] = 0xd9; - stream.Write(this.buf, 0, 2); + this.buffer[0] = 0xff; + this.buffer[1] = 0xd9; + stream.Write(this.buffer, 0, 2); stream.Flush(); } - // div returns a/b rounded to the nearest integer, instead of rounded to zero. - private static int div(int a, int b) + /// + /// Gets the quotient of the two numbers rounded to the nearest integer, instead of rounded to zero. + /// + /// The value to divide. + /// The value to divide by. + /// The + private static int Round(int dividend, int divisor) + { + if (dividend >= 0) + { + return (dividend + (divisor >> 1)) / divisor; + } + + return -((-dividend + (divisor >> 1)) / divisor); + } + + /// + /// Writes the Define Quantization Marker and tables. + /// + private void WriteDQT() + { + int markerlen = 2 + nQuantIndex * (1 + Block.BlockSize); + this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen); + for (int i = 0; i < nQuantIndex; i++) + { + this.writeByte((byte)i); + this.outputStream.Write(this.quant[i], 0, this.quant[i].Length); + } + } + + /// + /// Writes the Start Of Frame (Baseline) marker + /// + /// The width of the image + /// The height of the image + /// + private void WriteSOF0(int width, int height, int componentCount) + { + // "default" to 4:2:0 + byte[] subsamples = { 0x22, 0x11, 0x11 }; + byte[] chroma = { 0x00, 0x01, 0x01 }; + + switch (this.subsample) + { + case JpegSubsample.Ratio444: + subsamples = new byte[] { 0x11, 0x11, 0x11 }; + break; + case JpegSubsample.Ratio420: + subsamples = new byte[] { 0x22, 0x11, 0x11 }; + break; + } + + // Length (high byte, low byte), 8 + components * 3. + int markerlen = 8 + 3 * componentCount; + this.WriteMarkerHeader(JpegConstants.Markers.SOF0, markerlen); + this.buffer[0] = 8; // Data Precision. 8 for now, 12 and 16 bit jpegs not supported + this.buffer[1] = (byte)(height >> 8); + this.buffer[2] = (byte)(height & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported + this.buffer[3] = (byte)(width >> 8); + this.buffer[4] = (byte)(width & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported + this.buffer[5] = (byte)componentCount; // Number of components (1 byte), usually 1 = grey scaled, 3 = color YCbCr or YIQ, 4 = color CMYK) + if (componentCount == 1) + { + this.buffer[6] = 1; + + // No subsampling for grayscale images. + this.buffer[7] = 0x11; + this.buffer[8] = 0x00; + } + else + { + for (int i = 0; i < componentCount; i++) + { + this.buffer[3 * i + 6] = (byte)(i + 1); + + // We use 4:2:0 chroma subsampling by default. + this.buffer[3 * i + 7] = subsamples[i]; + this.buffer[3 * i + 8] = chroma[i]; + } + } + + this.outputStream.Write(this.buffer, 0, 3 * (componentCount - 1) + 9); + } + + /// + /// Writes the Define Huffman Table marker and tables. + /// + /// The number of components to write. + private void WriteDHT(int nComponent) + { + byte[] headers = { 0x00, 0x10, 0x01, 0x11 }; + int markerlen = 2; + huffmanSpec[] specs = this.theHuffmanSpec; + + if (nComponent == 1) + { + // Drop the Chrominance tables. + specs = new[] { this.theHuffmanSpec[0], this.theHuffmanSpec[1] }; + } + + foreach (var s in specs) + { + markerlen += 1 + 16 + s.values.Length; + } + + this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen); + for (int i = 0; i < specs.Length; i++) + { + huffmanSpec spec = specs[i]; + + this.writeByte(headers[i]); + this.outputStream.Write(spec.count, 0, spec.count.Length); + this.outputStream.Write(spec.values, 0, spec.values.Length); + } + } + + /// + /// Writes the StartOfScan marker. + /// + /// The pixel accessor providing acces to the image pixels. + private void WriteSOS(PixelAccessor pixels) + { + // TODO: We should allow grayscale writing. + this.outputStream.Write(this.SOSHeaderYCbCr, 0, this.SOSHeaderYCbCr.Length); + + switch (this.subsample) + { + case JpegSubsample.Ratio444: + this.Encode444(pixels); + break; + case JpegSubsample.Ratio420: + this.Encode420(pixels); + break; + } + + // Pad the last byte with 1's. + this.emit(0x7f, 7); + } + + /// + /// Encodes the image with no subsampling. + /// + /// The pixel accessor providing acces to the image pixels. + private void Encode444(PixelAccessor pixels) + { + Block b = new Block(); + Block cb = new Block(); + Block cr = new Block(); + int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; + + for (int y = 0; y < pixels.Height; y += 8) + { + for (int x = 0; x < pixels.Width; x += 8) + { + this.toYCbCr(pixels, x, y, b, cb, cr); + prevDCY = this.writeBlock(b, (quantIndex)0, prevDCY); + prevDCCb = this.writeBlock(cb, (quantIndex)1, prevDCCb); + prevDCCr = this.writeBlock(cr, (quantIndex)1, prevDCCr); + } + } + } + + /// + /// Encodes the image with subsampling. The Cb and Cr components are each subsampled + /// at a factor of 2 both horizontally and vertically. + /// + /// The pixel accessor providing acces to the image pixels. + private void Encode420(PixelAccessor pixels) + { + Block b = new Block(); + Block[] cb = new Block[4]; + Block[] cr = new Block[4]; + int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; + + for (int i = 0; i < 4; i++) cb[i] = new Block(); + for (int i = 0; i < 4; i++) cr[i] = new Block(); + + for (int y = 0; y < pixels.Height; y += 16) + { + for (int x = 0; x < pixels.Width; x += 16) + { + for (int i = 0; i < 4; i++) + { + int xOff = (i & 1) * 8; + int yOff = (i & 2) * 4; + + this.toYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); + prevDCY = this.writeBlock(b, (quantIndex)0, prevDCY); + } + + this.scale_16x16_8x8(b, cb); + prevDCCb = this.writeBlock(b, (quantIndex)1, prevDCCb); + this.scale_16x16_8x8(b, cr); + prevDCCr = this.writeBlock(b, (quantIndex)1, prevDCCr); + } + } + } + + /// + /// Writes the header for a marker with the given length. + /// + /// The marker to write. + /// The marker length. + private void WriteMarkerHeader(byte marker, int length) { - if (a >= 0) return (a + (b >> 1)) / b; - else return -((-a + (b >> 1)) / b); + // Markers are always prefixed with with 0xff. + this.buffer[0] = JpegConstants.Markers.XFF; + this.buffer[1] = marker; + this.buffer[2] = (byte)(length >> 8); + this.buffer[3] = (byte)(length & 0xff); + this.outputStream.Write(this.buffer, 0, 4); } } } From daa81f682ab6119d4a6f4678a73542d7d507addf Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 26 Jul 2016 13:47:33 +1000 Subject: [PATCH 6/8] More encoder cleanup. Former-commit-id: fe7074b76c3bb682667c1f00077a87373cf15b4b Former-commit-id: 875477a03fa94ab5c72ab2ebd4fc8303a36403fe Former-commit-id: b06f1d713c57778d7d6d29dd2e6000becc5ff929 --- .../Formats/Jpg/JpegEncoderCore.cs | 518 ++++++++++-------- 1 file changed, 295 insertions(+), 223 deletions(-) diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs index d2a7e35e0..384f18b36 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs @@ -9,35 +9,12 @@ namespace ImageProcessorCore.Formats internal class JpegEncoderCore { - // "APPlication specific" markers aren't part of the JPEG spec per se, - // but in practice, their use is described at - // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html - private const int app0Marker = 0xe0; - - private const int app14Marker = 0xee; - - private const int app15Marker = 0xef; - - // bitCount counts the number of bits needed to hold an integer. - private readonly byte[] bitCount = - { - 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, - }; - - // unzig maps from the zig-zag ordering to the natural ordering. For example, - // unzig[3] is the column and row of the fourth element in zig-zag order. The - // value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). - private static readonly int[] unzig = new[] + /// + /// Maps from the zig-zag ordering to the natural ordering. For example, + /// unzig[3] is the column and row of the fourth element in zig-zag order. The + /// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). + /// + private static readonly int[] Unzig = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, @@ -45,165 +22,154 @@ namespace ImageProcessorCore.Formats 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, }; - private const int nQuantIndex = 2; + private const int NQuantIndex = 2; - private const int nHuffIndex = 4; - - private enum quantIndex - { - quantIndexLuminance = 0, - - quantIndexChrominance = 1, - } - - private enum huffIndex + /// + /// Counts the number of bits needed to hold an integer. + /// + private readonly byte[] bitCount = { - huffIndexLuminanceDC = 0, - - huffIndexLuminanceAC = 1, - - huffIndexChrominanceDC = 2, - - huffIndexChrominanceAC = 3, - } + 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, + }; - // unscaledQuant are the unscaled quantization tables in zig-zag order. Each - // encoder copies and scales the tables according to its quality parameter. - // The values are derived from section K.1 after converting from natural to - // zig-zag order. - private byte[,] unscaledQuant = new byte[,] - { - { - // Luminance. - 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, 40, - 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, 57, 51, - 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, 109, 81, - 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, 100, 120, 92, - 101, 103, 99, - }, - { - // Chrominance. - 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, - }, - }; - - private class huffmanSpec - { - public huffmanSpec(byte[] c, byte[] v) + /// + /// The unscaled quantization tables in zig-zag order. Each + /// encoder copies and scales the tables according to its quality parameter. + /// The values are derived from section K.1 after converting from natural to + /// zig-zag order. + /// + private readonly byte[,] unscaledQuant = { + { + // Luminance. + 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, 40, + 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, 57, 51, + 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, 109, 81, + 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, 100, 120, 92, + 101, 103, 99, + }, { - this.count = c; - this.values = v; + // Chrominance. + 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, } + }; - public byte[] count; - - public byte[] values; - } - - // theHuffmanSpec is the Huffman encoding specifications. - // This encoder uses the same Huffman encoding for all images. - private huffmanSpec[] theHuffmanSpec = new[] - { + /// + /// The Huffman encoding specifications. + /// This encoder uses the same Huffman encoding for all images. + /// + private readonly HuffmanSpec[] theHuffmanSpec = { // Luminance DC. - new huffmanSpec( + new HuffmanSpec( new byte[] - { - 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 - }, + { + 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 + }, new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }), - new huffmanSpec( + new HuffmanSpec( new byte[] - { - 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125 - }, + { + 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125 + }, new byte[] - { - 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, - 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, - 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, - 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, - 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, - 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, - 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, - 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, - 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, - 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, - 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, - 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, - 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, - 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, - 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, - 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, - 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa - }), - new huffmanSpec( + { + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, + 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, + 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, + 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, + 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, + 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, + 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa + }), + new HuffmanSpec( new byte[] - { - 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 - }, + { + 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 + }, new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }), // Chrominance AC. - new huffmanSpec( + new HuffmanSpec( new byte[] - { - 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119 - }, + { + 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119 + }, new byte[] - { - 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, - 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, - 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, - 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, - 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, - 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, - 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, - 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, - 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, - 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, - 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, - 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, - 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, - 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, - 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, - 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, - 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, - 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, - }) + { + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, + 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, + 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, + 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, + 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, + 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, + 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, + 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, + 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, + 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, + 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, + 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, + }) }; - // huffmanLUT is a compiled look-up table representation of a huffmanSpec. - // Each value maps to a uint32 of which the 8 most significant bits hold the - // codeword size in bits and the 24 least significant bits hold the codeword. - // The maximum codeword size is 16 bits. - private class huffmanLUT + /// + /// A compiled look-up table representation of a huffmanSpec. + /// Each value maps to a uint32 of which the 8 most significant bits hold the + /// codeword size in bits and the 24 least significant bits hold the codeword. + /// The maximum codeword size is 16 bits. + /// + private class HuffmanLut { - public uint[] values; + public readonly uint[] Values; - public huffmanLUT(huffmanSpec s) + public HuffmanLut(HuffmanSpec s) { int maxValue = 0; - foreach (var v in s.values) + foreach (var v in s.Values) { if (v > maxValue) maxValue = v; } - this.values = new uint[maxValue + 1]; + this.Values = new uint[maxValue + 1]; int code = 0; int k = 0; - for (int i = 0; i < s.count.Length; i++) + for (int i = 0; i < s.Count.Length; i++) { int nBits = (i + 1) << 24; - for (int j = 0; j < s.count[i]; j++) + for (int j = 0; j < s.Count[i]; j++) { - this.values[s.values[k]] = (uint)(nBits | code); + this.Values[s.Values[k]] = (uint)(nBits | code); code++; k++; } @@ -217,32 +183,52 @@ namespace ImageProcessorCore.Formats // writing. All attempted writes after the first error become no-ops. private Stream outputStream; - // buf is a scratch buffer. - private byte[] buffer = new byte[16]; + /// + /// A scratch buffer to reduce allocations. + /// + private readonly byte[] buffer = new byte[16]; - // bits and nBits are accumulated bits to write to w. + /// + /// The accumulated bits to write to the stream. + /// private uint bits; + /// + /// The accumulated bits to write to the stream. + /// private uint nBits; - // quant is the scaled quantization tables, in zig-zag order. - private byte[][] quant = new byte[nQuantIndex][]; // [Block.blockSize]; + /// + /// The scaled quantization tables, in zig-zag order. + /// + private readonly byte[][] quant = new byte[NQuantIndex][]; // [Block.blockSize]; - // theHuffmanLUT are compiled representations of theHuffmanSpec. - private huffmanLUT[] theHuffmanLUT = new huffmanLUT[4]; + // The compiled representations of theHuffmanSpec. + private readonly HuffmanLut[] theHuffmanLUT = new HuffmanLut[4]; + /// + /// The subsampling method to use. + /// private JpegSubsample subsample; - private void writeByte(byte b) + /// + /// Writes the given byte to the stream. + /// + /// + private void WriteByte(byte b) { var data = new byte[1]; data[0] = b; this.outputStream.Write(data, 0, 1); } - // emit emits the least significant nBits bits of bits to the bit-stream. - // The precondition is bits < 1< + /// Emits the least significant nBits bits of bits to the bit-stream. + /// The precondition is bits < 1<<nBits && nBits <= 16. + /// + /// + /// + private void Emit(uint bits, uint nBits) { nBits += this.nBits; bits <<= (int)(32 - nBits); @@ -250,8 +236,8 @@ namespace ImageProcessorCore.Formats while (nBits >= 8) { byte b = (byte)(bits >> 24); - this.writeByte(b); - if (b == 0xff) this.writeByte(0x00); + this.WriteByte(b); + if (b == 0xff) this.WriteByte(0x00); bits <<= 8; nBits -= 8; } @@ -260,52 +246,75 @@ namespace ImageProcessorCore.Formats this.nBits = nBits; } - // emitHuff emits the given value with the given Huffman encoder. - private void emitHuff(huffIndex h, int v) + /// + /// Emits the given value with the given Huffman encoder. + /// + /// The index of the Huffman encoder + /// The value to encode. + private void EmitHuff(HuffIndex index, int value) { - uint x = this.theHuffmanLUT[(int)h].values[v]; - this.emit(x & ((1 << 24) - 1), x >> 24); + uint x = this.theHuffmanLUT[(int)index].Values[value]; + this.Emit(x & ((1 << 24) - 1), x >> 24); } - // emitHuffRLE emits a run of runLength copies of value encoded with the given - // Huffman encoder. - private void emitHuffRLE(huffIndex h, int runLength, int v) + /// + /// Emits a run of runLength copies of value encoded with the given Huffman encoder. + /// + /// The index of the Huffman encoder + /// The number of copies to encode. + /// The value to encode. + private void EmitHuffRLE(HuffIndex index, int runLength, int value) { - int a = v; - int b = v; + int a = value; + int b = value; if (a < 0) { - a = -v; - b = v - 1; + a = -value; + b = value - 1; } - uint nBits = 0; - if (a < 0x100) nBits = this.bitCount[a]; - else nBits = 8 + (uint)this.bitCount[a >> 8]; + uint bt; + if (a < 0x100) + { + bt = this.bitCount[a]; + } + else + { + bt = 8 + (uint)this.bitCount[a >> 8]; + } - this.emitHuff(h, (int)((uint)(runLength << 4) | nBits)); - if (nBits > 0) this.emit((uint)b & (uint)((1 << ((int)nBits)) - 1), nBits); + this.EmitHuff(index, (int)((uint)(runLength << 4) | bt)); + if (bt > 0) + { + this.Emit((uint)b & (uint)((1 << ((int)bt)) - 1), bt); + } } - // writeBlock writes a block of pixel data using the given quantization table, - // returning the post-quantized DC value of the DCT-transformed block. b is in - // natural (not zig-zag) order. - private int writeBlock(Block b, quantIndex q, int prevDC) + /// + /// Writes a block of pixel data using the given quantization table, + /// returning the post-quantized DC value of the DCT-transformed block. + /// The block is in natural (not zig-zag) order. + /// + /// The block to write. + /// The quantization table index. + /// The previous DC value. + /// + private int WriteBlock(Block block, QuantIndex index, int prevDC) { - FDCT.Transform(b); + FDCT.Transform(block); // Emit the DC delta. - int dc = Round(b[0], 8 * this.quant[(int)q][0]); - this.emitHuffRLE((huffIndex)(2 * (int)q + 0), 0, dc - prevDC); + int dc = Round(block[0], 8 * this.quant[(int)index][0]); + this.EmitHuffRLE((HuffIndex)(2 * (int)index + 0), 0, dc - prevDC); // Emit the AC components. - var h = (huffIndex)(2 * (int)q + 1); + var h = (HuffIndex)(2 * (int)index + 1); int runLength = 0; for (int zig = 1; zig < Block.BlockSize; zig++) { - int ac = Round(b[unzig[zig]], 8 * this.quant[(int)q][zig]); + int ac = Round(block[Unzig[zig]], 8 * this.quant[(int)index][zig]); if (ac == 0) { @@ -315,22 +324,22 @@ namespace ImageProcessorCore.Formats { while (runLength > 15) { - this.emitHuff(h, 0xf0); + this.EmitHuff(h, 0xf0); runLength -= 16; } - this.emitHuffRLE(h, runLength, ac); + this.EmitHuffRLE(h, runLength, ac); runLength = 0; } } - if (runLength > 0) this.emitHuff(h, 0x00); + if (runLength > 0) this.EmitHuff(h, 0x00); return dc; } // toYCbCr converts the 8x8 region of m whose top-left corner is p to its // YCbCr values. - private void toYCbCr(PixelAccessor pixels, int x, int y, Block yBlock, Block cbBlock, Block crBlock) + private void ToYCbCr(PixelAccessor pixels, int x, int y, Block yBlock, Block cbBlock, Block crBlock) { int xmax = pixels.Width - 1; int ymax = pixels.Height - 1; @@ -347,9 +356,13 @@ namespace ImageProcessorCore.Formats } } - // scale scales the 16x16 region represented by the 4 src blocks to the 8x8 - // dst block. - private void scale_16x16_8x8(Block dst, Block[] src) + /// + /// Scales the 16x16 region represented by the 4 src blocks to the 8x8 + /// dst block. + /// + /// The destination block array + /// The source block array. + private void Scale16X16_8X8(Block destination, Block[] source) { for (int i = 0; i < 4; i++) { @@ -359,8 +372,8 @@ namespace ImageProcessorCore.Formats for (int x = 0; x < 4; x++) { int j = 16 * y + 2 * x; - int sum = src[i][j] + src[i][j + 1] + src[i][j + 8] + src[i][j + 9]; - dst[8 * y + x + dstOff] = (sum + 2) / 4; + int sum = source[i][j] + source[i][j + 1] + source[i][j + 8] + source[i][j + 9]; + destination[8 * y + x + dstOff] = (sum + 2) / 4; } } } @@ -429,10 +442,10 @@ namespace ImageProcessorCore.Formats // TODO: This should be static should it not? for (int i = 0; i < this.theHuffmanSpec.Length; i++) { - this.theHuffmanLUT[i] = new huffmanLUT(this.theHuffmanSpec[i]); + this.theHuffmanLUT[i] = new HuffmanLut(this.theHuffmanSpec[i]); } - for (int i = 0; i < nQuantIndex; i++) + for (int i = 0; i < NQuantIndex; i++) { this.quant[i] = new byte[Block.BlockSize]; } @@ -452,7 +465,7 @@ namespace ImageProcessorCore.Formats } // Initialize the quantization tables. - for (int i = 0; i < nQuantIndex; i++) + for (int i = 0; i < NQuantIndex; i++) { for (int j = 0; j < Block.BlockSize; j++) { @@ -516,11 +529,11 @@ namespace ImageProcessorCore.Formats /// private void WriteDQT() { - int markerlen = 2 + nQuantIndex * (1 + Block.BlockSize); + int markerlen = 2 + NQuantIndex * (1 + Block.BlockSize); this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen); - for (int i = 0; i < nQuantIndex; i++) + for (int i = 0; i < NQuantIndex; i++) { - this.writeByte((byte)i); + this.WriteByte((byte)i); this.outputStream.Write(this.quant[i], 0, this.quant[i].Length); } } @@ -587,7 +600,7 @@ namespace ImageProcessorCore.Formats { byte[] headers = { 0x00, 0x10, 0x01, 0x11 }; int markerlen = 2; - huffmanSpec[] specs = this.theHuffmanSpec; + HuffmanSpec[] specs = this.theHuffmanSpec; if (nComponent == 1) { @@ -597,17 +610,17 @@ namespace ImageProcessorCore.Formats foreach (var s in specs) { - markerlen += 1 + 16 + s.values.Length; + markerlen += 1 + 16 + s.Values.Length; } this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen); for (int i = 0; i < specs.Length; i++) { - huffmanSpec spec = specs[i]; + HuffmanSpec spec = specs[i]; - this.writeByte(headers[i]); - this.outputStream.Write(spec.count, 0, spec.count.Length); - this.outputStream.Write(spec.values, 0, spec.values.Length); + this.WriteByte(headers[i]); + this.outputStream.Write(spec.Count, 0, spec.Count.Length); + this.outputStream.Write(spec.Values, 0, spec.Values.Length); } } @@ -631,9 +644,11 @@ namespace ImageProcessorCore.Formats } // Pad the last byte with 1's. - this.emit(0x7f, 7); + this.Emit(0x7f, 7); } + + /// /// Encodes the image with no subsampling. /// @@ -649,10 +664,10 @@ namespace ImageProcessorCore.Formats { for (int x = 0; x < pixels.Width; x += 8) { - this.toYCbCr(pixels, x, y, b, cb, cr); - prevDCY = this.writeBlock(b, (quantIndex)0, prevDCY); - prevDCCb = this.writeBlock(cb, (quantIndex)1, prevDCCb); - prevDCCr = this.writeBlock(cr, (quantIndex)1, prevDCCr); + this.ToYCbCr(pixels, x, y, b, cb, cr); + prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY); + prevDCCb = this.WriteBlock(cb, QuantIndex.Chrominance, prevDCCb); + prevDCCr = this.WriteBlock(cr, QuantIndex.Chrominance, prevDCCr); } } } @@ -681,14 +696,14 @@ namespace ImageProcessorCore.Formats int xOff = (i & 1) * 8; int yOff = (i & 2) * 4; - this.toYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); - prevDCY = this.writeBlock(b, (quantIndex)0, prevDCY); + this.ToYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]); + prevDCY = this.WriteBlock(b, QuantIndex.Luminance, prevDCY); } - this.scale_16x16_8x8(b, cb); - prevDCCb = this.writeBlock(b, (quantIndex)1, prevDCCb); - this.scale_16x16_8x8(b, cr); - prevDCCr = this.writeBlock(b, (quantIndex)1, prevDCCr); + this.Scale16X16_8X8(b, cb); + prevDCCb = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCb); + this.Scale16X16_8X8(b, cr); + prevDCCr = this.WriteBlock(b, QuantIndex.Chrominance, prevDCCr); } } } @@ -707,5 +722,62 @@ namespace ImageProcessorCore.Formats this.buffer[3] = (byte)(length & 0xff); this.outputStream.Write(this.buffer, 0, 4); } + + /// + /// Enumerates the Huffman tables + /// + private enum HuffIndex + { + LuminanceDC = 0, + + LuminanceAC = 1, + + ChrominanceDC = 2, + + ChrominanceAC = 3, + } + + /// + /// Enumerates the quantization tables + /// + private enum QuantIndex + { + /// + /// Luminance + /// + Luminance = 0, + + /// + /// Chrominance + /// + Chrominance = 1, + } + + /// + /// The Huffman encoding specifications. + /// + private struct HuffmanSpec + { + /// + /// Initializes a n ew instance of the struct. + /// + /// The number of codes. + /// The decoded values. + public HuffmanSpec(byte[] count, byte[] values) + { + this.Count = count; + this.Values = values; + } + + /// + /// Gets count[i] - The number of codes of length i bits. + /// + public readonly byte[] Count; + + /// + /// Gets value[i] - The decoded value of the i'th codeword. + /// + public readonly byte[] Values; + } } } From 7faffa465d33699e59bb62d2c873af592d599a12 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 26 Jul 2016 13:53:46 +1000 Subject: [PATCH 7/8] Use "this" Former-commit-id: d4832e3e32185f30e5654c3abfb717e5ed44cfa2 Former-commit-id: 13f722c467d87236599c45bb0e4fe1f95a080740 Former-commit-id: 9204894e3776899bbf5ee33e2e35b953eb41ca35 --- .../Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id index 8d3e51867..72b7f5308 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id +++ b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id @@ -1 +1 @@ -af4a353d1d290be5dd231656bdb558fcdc143805 \ No newline at end of file +b643c4d82973c427e30384f20fdc46b29d913782 \ No newline at end of file From b8b588d22122c4288e8922148a276922bf5be269 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 26 Jul 2016 16:00:50 +1000 Subject: [PATCH 8/8] Now decodes grayscale inside the core decoder Former-commit-id: 69bc6a527f9d013eba29649a5d6b0893cc9a1317 Former-commit-id: 0185f5dc2e1f738ce87f757c7427fdc4c6cb8335 Former-commit-id: 138fa9716bb44d037b7e5dbd014bcfbe9a8401e7 --- .../Formats/Bmp/BmpDecoderCore.cs | 2 +- .../Formats/Jpg/JpegConstants.cs | 29 ++++++++++++++++ .../Formats/Jpg/JpegDecoder.cs | 34 ------------------- .../Jpg/JpegDecoderCore.cs.REMOVED.git-id | 2 +- .../ImageProcessorCore.Tests/FileTestBase.cs | 12 +++---- 5 files changed, 37 insertions(+), 42 deletions(-) diff --git a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs index da92e6a63..fadfc1ec6 100644 --- a/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageProcessorCore/Formats/Bmp/BmpDecoderCore.cs @@ -51,7 +51,7 @@ namespace ImageProcessorCore.Formats /// /// The image, where the data should be set to. /// Cannot be null (Nothing in Visual Basic). - /// The this._stream, where the image should be + /// The stream, where the image should be /// decoded from. Cannot be null (Nothing in Visual Basic). /// /// is null. diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs b/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs index 22c6a8c9b..4df086f29 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegConstants.cs @@ -143,6 +143,24 @@ namespace ImageProcessorCore.Formats /// public const byte DRI = 0xdd; + /// + /// Define First Restart + /// + /// Inserted every r macroblocks, where r is the restart interval set by a DRI marker. + /// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7. + /// + /// + public const byte RST0 = 0xd0; + + /// + /// Define Eigth Restart + /// + /// Inserted every r macroblocks, where r is the restart interval set by a DRI marker. + /// Not used if there was no DRI marker. The low three bits of the marker code cycle in value from 0 to 7. + /// + /// + public const byte RST7 = 0xd7; + /// /// Start of Scan /// @@ -168,6 +186,7 @@ namespace ImageProcessorCore.Formats /// /// Application specific marker for marking the jpeg format. + /// /// public const byte APP0 = 0xe0; @@ -175,6 +194,16 @@ namespace ImageProcessorCore.Formats /// Application specific marker for marking where to store metadata. /// public const byte APP1 = 0xe1; + + /// + /// Application specific marker used by Adobe for storing encoding information for DCT filters. + /// + public const byte APP14 = 0xee; + + /// + /// Application specific marker used by GraphicConverter to store JPEG quality. + /// + public const byte APP15 = 0xef; } } } \ No newline at end of file diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegDecoder.cs b/src/ImageProcessorCore/Formats/Jpg/JpegDecoder.cs index 34e0932dc..89aa4e001 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegDecoder.cs +++ b/src/ImageProcessorCore/Formats/Jpg/JpegDecoder.cs @@ -7,7 +7,6 @@ namespace ImageProcessorCore.Formats { using System; using System.IO; - using System.Threading.Tasks; /// /// Image decoder for generating an image out of a jpg stream. @@ -96,39 +95,6 @@ namespace ImageProcessorCore.Formats JpegDecoderCore decoder = new JpegDecoderCore(); decoder.Decode(stream, image, false); - - // TODO: When nComp is 3 we set the ImageBase pixels internally, Eventually we should - // do the same here - if (decoder.nComp == 1) - { - int pixelWidth = decoder.width; - int pixelHeight = decoder.height; - - float[] pixels = new float[pixelWidth * pixelHeight * 4]; - - Parallel.For( - 0, - pixelHeight, - y => - { - var yoff = decoder.img1.get_row_offset(y); - for (int x = 0; x < pixelWidth; x++) - { - int offset = ((y * pixelWidth) + x) * 4; - - pixels[offset + 0] = decoder.img1.pixels[yoff + x] / 255f; - pixels[offset + 1] = decoder.img1.pixels[yoff + x] / 255f; - pixels[offset + 2] = decoder.img1.pixels[yoff + x] / 255f; - pixels[offset + 3] = 1; - } - }); - - image.SetPixels(pixelWidth, pixelHeight, pixels); - } - else if (decoder.nComp != 3) - { - throw new NotSupportedException("JpegDecoder only supports RGB and Grayscale color spaces."); - } } /// diff --git a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id index 72b7f5308..a5c139745 100644 --- a/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id +++ b/src/ImageProcessorCore/Formats/Jpg/JpegDecoderCore.cs.REMOVED.git-id @@ -1 +1 @@ -b643c4d82973c427e30384f20fdc46b29d913782 \ No newline at end of file +7a5076971068e0f389da2fb1e8b25216f4049718 \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/FileTestBase.cs b/tests/ImageProcessorCore.Tests/FileTestBase.cs index 100e74d0f..f5c83cd71 100644 --- a/tests/ImageProcessorCore.Tests/FileTestBase.cs +++ b/tests/ImageProcessorCore.Tests/FileTestBase.cs @@ -19,17 +19,17 @@ namespace ImageProcessorCore.Tests /// protected static readonly List Files = new List { - //"TestImages/Formats/Jpg/Floorplan.jpeg", // Perf: Enable for local testing only + "TestImages/Formats/Jpg/Floorplan.jpeg", // Perf: Enable for local testing only "TestImages/Formats/Jpg/Calliphora.jpg", - //"TestImages/Formats/Jpg/fb.jpg", // Perf: Enable for local testing only - //"TestImages/Formats/Jpg/progress.jpg", // Perf: Enable for local testing only + "TestImages/Formats/Jpg/fb.jpg", // Perf: Enable for local testing only + "TestImages/Formats/Jpg/progress.jpg", // Perf: Enable for local testing only //"TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg", // Perf: Enable for local testing only - "TestImages/Formats/Bmp/Car.bmp", + //"TestImages/Formats/Bmp/Car.bmp", // "TestImages/Formats/Bmp/neg_height.bmp", // Perf: Enable for local testing only //"TestImages/Formats/Png/blur.png", // Perf: Enable for local testing only //"TestImages/Formats/Png/indexed.png", // Perf: Enable for local testing only - "TestImages/Formats/Png/splash.png", - "TestImages/Formats/Gif/rings.gif", + //"TestImages/Formats/Png/splash.png", + //"TestImages/Formats/Gif/rings.gif", //"TestImages/Formats/Gif/giphy.gif" // Perf: Enable for local testing only };