From 0ad8ca517cba28257b1a3226bbdc614f4cf03f4e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 25 Jul 2016 14:11:04 +1000 Subject: [PATCH] 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 91dab75c69..22c6a8c9b5 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 3274907d59..d2a7e35e05 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); } } }