diff --git a/src/Constants.cs b/src/Constants.cs index fb923ca..8f2d0b6 100644 --- a/src/Constants.cs +++ b/src/Constants.cs @@ -39,5 +39,8 @@ internal static class Constants // The size of a SPIFF header when serialized to a JPEG byte stream. internal const int SpiffHeaderSizeInBytes = 34; + // The maximum size of the data bytes that fit in a segment. + internal const int SegmentMaxDataSize = ushort.MaxValue - SegmentLengthSize; + internal const int Int32BitCount = 32; } diff --git a/src/JpegLSDecoder.cs b/src/JpegLSDecoder.cs index e972382..acea5e5 100644 --- a/src/JpegLSDecoder.cs +++ b/src/JpegLSDecoder.cs @@ -11,7 +11,6 @@ namespace CharLS.JpegLS; public sealed class JpegLSDecoder { private FrameInfo? _frameInfo; - //private JpegLSInterleaveMode? _interleaveMode; private readonly JpegStreamReader _reader = new(); private enum State @@ -87,7 +86,7 @@ public ReadOnlyMemory Source /// /// The frame information of the parsed JPEG-LS image. /// - /// Thrown when this property is used before . + /// Thrown when this property is used before . public FrameInfo FrameInfo => _reader.FrameInfo ?? throw new InvalidOperationException("Incorrect state. ReadHeader has not called."); /// @@ -99,7 +98,7 @@ public ReadOnlyMemory Source /// /// The near lossless parameter. A value of 0 means that the image is lossless encoded. /// - /// Thrown when this property is used before . + /// Thrown when this property is used before . public int NearLossless { get @@ -116,7 +115,7 @@ public int NearLossless /// Property should be obtained after calling ". /// /// The result of the operation: success or a failure code. - /// Thrown when this property is used before . + /// Thrown when this property is used before . public InterleaveMode InterleaveMode { get @@ -132,8 +131,15 @@ public InterleaveMode InterleaveMode /// /// The preset coding parameters. /// - /// Thrown when this property is used before . - public JpegLSPresetCodingParameters PresetCodingParameters => _reader.JpegLSPresetCodingParameters ?? new JpegLSPresetCodingParameters(); + /// Thrown when this property is used before . + public JpegLSPresetCodingParameters PresetCodingParameters + { + get + { + CheckHeaderRead(); + return _reader.JpegLSPresetCodingParameters ?? new JpegLSPresetCodingParameters(); + } + } /// /// Returns the HP color transformation that was used to encode the scan. @@ -152,7 +158,7 @@ public ColorTransformation ColorTransformation /// The stride to use; byte count to the next pixel row. Pass 0 for the default. /// The size of the destination buffer in bytes. /// When the required destination size doesn't fit in an int. - /// Thrown when this method is called before . + /// Thrown when this method is called before . public int GetDestinationSize(int stride = 0) { if (_state < State.HeaderRead) @@ -179,44 +185,22 @@ public int GetDestinationSize(int stride = 0) } } - ///// - ///// Reads the SPIFF (Still Picture Interchange File Format) header. - ///// - ///// The header or null when no valid header was found. - ///// true if a SPIFF header was present and could be read. - ///// Thrown when the instance is used after being disposed. - //public bool TryReadSpiffHeader(out SpiffHeader? spiffHeader) - //{ - // bool found = headerFound != 0; - // if (found) - // { - // found = SpiffHeader.TryCreate(headerNative, out spiffHeader); - // } - // else - // { - // spiffHeader = default; - // } - - // return found; - //} - /// /// Reads the header of the JPEG-LS stream. /// After calling this method, the informational properties can be obtained. /// - /// if set to true try to read the SPIFF header first. /// Thrown when the JPEG-LS stream is not valid. - public void ReadHeader(bool tryReadSpiffHeader = true) + public void ReadHeader() { CheckOperation(_state == State.SourceSet); _reader.ReadHeader(); _state = State.HeaderRead; - //if (tryReadSpiffHeader && TryReadSpiffHeader(out SpiffHeader? spiffHeader)) - //{ - // SpiffHeader = spiffHeader; - //} + if (_reader.SpiffHeader != null && _reader.SpiffHeader.IsValid(FrameInfo)) + { + SpiffHeader = _reader.SpiffHeader; + } } /// @@ -226,7 +210,7 @@ public void ReadHeader(bool tryReadSpiffHeader = true) /// A byte array with the decoded JPEG-LS data. /// Thrown when the JPEG-LS stream is not valid. /// Thrown when the instance is used after being disposed. - /// Thrown when this method is called before . + /// Thrown when this method is called before . public byte[] Decode(int stride = 0) { var destination = new byte[GetDestinationSize()]; diff --git a/src/JpegStreamWriter.cs b/src/JpegStreamWriter.cs index f973bf3..236c291 100644 --- a/src/JpegStreamWriter.cs +++ b/src/JpegStreamWriter.cs @@ -13,13 +13,19 @@ internal class JpegStreamWriter internal Memory Destination { get; set; } + // ReSharper disable once ConvertToAutoPropertyWhenPossible + internal int BytesWritten => _position; + internal Memory GetRemainingDestination() { return Destination[_position..]; } - // ReSharper disable once ConvertToAutoPropertyWhenPossible - internal int BytesWritten => _position; + internal void Rewind() + { + _position = 0; + _componentIndex = 0; + } internal void Seek(int byteCount) { @@ -32,6 +38,56 @@ internal void WriteStartOfImage() WriteSegmentWithoutData(JpegMarkerCode.StartOfImage); } + /// + /// Write a JPEG SPIFF (APP8 + spiff) segment. + /// This segment is documented in ISO/IEC 10918-3, Annex F. + /// + internal void WriteSpiffHeaderSegment(SpiffHeader header) + { + Debug.Assert(header.Height > 0); + Debug.Assert(header.Width > 0); + + Span spiffMagicId = [(byte)'S', (byte)'P', (byte)'I', (byte)'F', (byte)'F', (byte)'\0']; + + // Create a JPEG APP8 segment in Still Picture Interchange File Format (SPIFF), v2.0 + WriteSegmentHeader(JpegMarkerCode.ApplicationData8, 30); + WriteBytes(spiffMagicId); + WriteByte(Constants.SpiffMajorRevisionNumber); + WriteByte(Constants.SpiffMinorRevisionNumber); + WriteByte((byte)header.ProfileId); + WriteByte((byte)header.ComponentCount); + WriteUint32(header.Height); + WriteUint32(header.Width); + WriteByte((byte)header.ColorSpace); + WriteByte((byte)header.BitsPerSample); + WriteByte((byte)header.CompressionType); + WriteByte((byte)header.ResolutionUnit); + WriteUint32(header.VerticalResolution); + WriteUint32(header.HorizontalResolution); + } + + internal void WriteSpiffDirectoryEntry(int entryTag, Span entryData) + { + WriteSegmentHeader(JpegMarkerCode.ApplicationData8, sizeof(int) + entryData.Length); + WriteUint32(entryTag); + WriteBytes(entryData); + } + + internal void WriteSpiffEndOfDirectoryEntry() + { + // Note: ISO/IEC 10918-3, Annex F.2.2.3 documents that the EOD entry segment should have a length of 8 + // but only 6 data bytes. This approach allows to wrap existing bit streams\encoders with a SPIFF header. + // In this implementation the SOI marker is added as data bytes to simplify the stream writer design. + Span spiffEndOfDirectory = + [ + 0, 0, + 0, Constants.SpiffEndOfDirectoryEntryType, + 0xFF, (byte) JpegMarkerCode.StartOfImage + ]; + + WriteSegment(JpegMarkerCode.ApplicationData8, spiffEndOfDirectory); + } + internal void WriteColorTransformSegment(ColorTransformation colorTransformation) { byte[] segment = [(byte)'m', (byte)'r', (byte)'f', (byte)'x', (byte)colorTransformation]; @@ -128,7 +184,7 @@ internal void WriteEndOfImage(bool evenDestinationSize) private void WriteSegmentWithoutData(JpegMarkerCode markerCode) { if (_position + 2 > Destination.Length) - throw Util.CreateInvalidDataException(ErrorCode.DestinationBufferTooSmall); + ThrowHelper.ThrowArgumentOutOfRangeException(ErrorCode.DestinationBufferTooSmall); WriteByte(Constants.JpegMarkerStartByte); WriteByte((byte)markerCode); @@ -142,15 +198,14 @@ private void WriteSegment(JpegMarkerCode markerCode, ReadOnlySpan data) private void WriteSegmentHeader(JpegMarkerCode markerCode, int dataSize) { - // ASSERT(data_size <= segment_max_data_size); + Debug.Assert(dataSize <= Constants.SegmentMaxDataSize); // Check if there is enough room in the destination to write the complete segment. // Other methods assume that the checking in done here and don't check again. - //const int markerCodeSize = 2; - //int totalSegmentSize = markerCodeSize + Constants.SegmentLengthSize + dataSize; - //if (const size_t total_segment_size{marker_code_size + segment_length_size + data_size}; - //UNLIKELY(byte_offset_ + total_segment_size > destination_.size())) - //impl::throw_jpegls_error(jpegls_errc::destination_buffer_too_small); + const int markerCodeSize = 2; + int totalSegmentSize = markerCodeSize + Constants.SegmentLengthSize + dataSize; + if (_position + totalSegmentSize > Destination.Length) + ThrowHelper.ThrowArgumentOutOfRangeException(ErrorCode.DestinationBufferTooSmall); WriteMarker(markerCode); WriteUint16(Constants.SegmentLengthSize + dataSize); diff --git a/src/SpiffHeader.cs b/src/SpiffHeader.cs index cfc02ea..2c67fb2 100644 --- a/src/SpiffHeader.cs +++ b/src/SpiffHeader.cs @@ -99,4 +99,75 @@ public SpiffHeader() /// The horizontal resolution. /// public int HorizontalResolution { get; init; } + + internal bool IsValid(FrameInfo frameInfo) + { + if (CompressionType != SpiffCompressionType.JpegLS) + return false; + + if (ProfileId != SpiffProfileId.None) + return false; + + if (!IsValidResolutionUnits(ResolutionUnit)) + return false; + + if (HorizontalResolution == 0 || VerticalResolution == 0) + return false; + + if (ComponentCount != frameInfo.ComponentCount) + return false; + + if (!IsValidColorSpace(ColorSpace, ComponentCount)) + return false; + + if (BitsPerSample != frameInfo.BitsPerSample) + return false; + + if (Height != frameInfo.Height) + return false; + + return Width == frameInfo.Width; + } + + private static bool IsValidResolutionUnits(SpiffResolutionUnit resolutionUnits) + { + return resolutionUnits switch + { + SpiffResolutionUnit.AspectRatio or + SpiffResolutionUnit.DotsPerInch or + SpiffResolutionUnit.DotsPerCentimeter => true, + _ => false + }; + } + + private static bool IsValidColorSpace(SpiffColorSpace colorSpace, int componentCount) + { + switch (colorSpace) + { + case SpiffColorSpace.None: + return true; + + case SpiffColorSpace.BiLevelBlack: + case SpiffColorSpace.BiLevelWhite: + return false; // not supported for JPEG-LS. + + case SpiffColorSpace.Grayscale: + return componentCount == 1; + + case SpiffColorSpace.YcbcrItuBT709Video: + case SpiffColorSpace.YcbcrItuBT6011Rgb: + case SpiffColorSpace.YcbcrItuBT6011Video: + case SpiffColorSpace.Rgb: + case SpiffColorSpace.Cmy: + case SpiffColorSpace.PhotoYcc: + case SpiffColorSpace.CieLab: + return componentCount == 3; + + case SpiffColorSpace.Cmyk: + case SpiffColorSpace.Ycck: + return componentCount == 4; + } + + return false; + } } diff --git a/src/ThrowHelper.cs b/src/ThrowHelper.cs new file mode 100644 index 0000000..89a0326 --- /dev/null +++ b/src/ThrowHelper.cs @@ -0,0 +1,30 @@ +// Copyright (c) Team CharLS. +// SPDX-License-Identifier: BSD-3-Clause + +using System.Diagnostics.CodeAnalysis; + +namespace CharLS.JpegLS; + +internal class ThrowHelper +{ + [DoesNotReturn] + internal static void ThrowArgumentOutOfRangeException(ErrorCode errorCode) + { + throw AddErrorCode(new ArgumentOutOfRangeException(GetErrorMessage(errorCode)), errorCode); + } + + private static Exception AddErrorCode(Exception exception, ErrorCode errorCode) + { + exception.Data.Add(nameof(ErrorCode), errorCode); + return exception; + } + + private static string GetErrorMessage(ErrorCode errorCode) + { + return errorCode switch + { + ErrorCode.None => "", + _ => "todo", + }; + } +} diff --git a/test/JpegLSDecoderTest.cs b/test/JpegLSDecoderTest.cs index 9c8c88e..157f3b7 100644 --- a/test/JpegLSDecoderTest.cs +++ b/test/JpegLSDecoderTest.cs @@ -18,12 +18,14 @@ public void SetSourceTwiceThrows() Assert.False(string.IsNullOrEmpty(exception.Message)); } - //TEST_METHOD(read_spiff_header_without_source) // NOLINT - //{ - // jpegls_decoder decoder; + [Fact] + public void ReadHeaderWithoutSource() + { + JpegLSDecoder decoder = new(); - // assert_expect_exception(jpegls_errc::invalid_operation, [&decoder] { decoder.read_spiff_header(); }); - //} + var exception = Assert.Throws(() => decoder.ReadHeader()); + Assert.False(string.IsNullOrEmpty(exception.Message)); + } [Fact] public void DestinationSizeWithoutReadingHeader() @@ -64,31 +66,35 @@ public void FrameInfoWithoutReadHeaderThrows() Assert.False(string.IsNullOrEmpty(exception.Message)); } - //TEST_METHOD(interleave_mode_without_read_header) // NOLINT - //{ - // const vector source(2000); - // const jpegls_decoder decoder{ source, false}; - - // assert_expect_exception(jpegls_errc::invalid_operation, [&decoder] { ignore = decoder.interleave_mode(); }); - //} + [Fact] + public void InterleaveModeWithoutReadHeader() + { + var buffer = new byte[2000]; + JpegLSDecoder decoder = new() { Source = buffer }; - //TEST_METHOD(near_lossless_without_read_header) // NOLINT - //{ - // const vector source(2000); - // const jpegls_decoder decoder{ source, false}; + var exception = Assert.Throws(() => decoder.InterleaveMode); + Assert.False(string.IsNullOrEmpty(exception.Message)); + } - // assert_expect_exception(jpegls_errc::invalid_operation, [&decoder] { ignore = decoder.near_lossless(); }); - //} + [Fact] + public void NearLosslessWithoutReadHeader() + { + var buffer = new byte[2000]; + JpegLSDecoder decoder = new() { Source = buffer }; - //TEST_METHOD(preset_coding_parameters_without_read_header) // NOLINT - //{ - // jpegls_decoder decoder; + var exception = Assert.Throws(() => decoder.NearLossless); + Assert.False(string.IsNullOrEmpty(exception.Message)); + } - // const vector source(2000); - // decoder.source(source); + [Fact] + public void PresetCodingParametersWithoutReadHeader() + { + var buffer = new byte[2000]; + JpegLSDecoder decoder = new() { Source = buffer }; - // assert_expect_exception(jpegls_errc::invalid_operation, [&decoder] { ignore = decoder.preset_coding_parameters(); }); - //} + var exception = Assert.Throws(() => decoder.PresetCodingParameters); + Assert.False(string.IsNullOrEmpty(exception.Message)); + } [Fact] public void DestinationSize() @@ -107,7 +113,7 @@ public void DestinationSize() // constexpr size_t stride{ 512}; // constexpr size_t expected_destination_size{ stride * 256 * 3}; - // Assert::AreEqual(expected_destination_size, decoder.destination_size(stride)); + // Assert.Equal(expected_destination_size, decoder.destination_size(stride)); //} //TEST_METHOD(destination_size_stride_interleave_line) // NOLINT @@ -118,7 +124,7 @@ public void DestinationSize() // constexpr size_t stride{ 1024}; // constexpr size_t expected_destination_size{ stride * 256}; - // Assert::AreEqual(expected_destination_size, decoder.destination_size(stride)); + // Assert.Equal(expected_destination_size, decoder.destination_size(stride)); //} //TEST_METHOD(destination_size_stride_interleave_sample) // NOLINT @@ -129,7 +135,7 @@ public void DestinationSize() // constexpr size_t stride = 1024; // constexpr size_t expected_destination_size{ stride * 256}; - // Assert::AreEqual(expected_destination_size, decoder.destination_size(stride)); + // Assert.Equal(expected_destination_size, decoder.destination_size(stride)); //} //TEST_METHOD(decode_reference_file_from_buffer) // NOLINT @@ -147,7 +153,7 @@ public void DestinationSize() // const auto&reference_image_data = reference_file.image_data(); // for (size_t i{ }; i != destination.size(); ++i) // { - // Assert::AreEqual(reference_image_data[i], destination[i]); + // Assert.Equal(reference_image_data[i], destination[i]); // } //} @@ -167,7 +173,7 @@ public void DestinationSize() // const auto&reference_image_data = reference_file.image_data(); // for (size_t i{ }; i != destination.size(); ++i) // { - // Assert::AreEqual(reference_image_data[i], destination[i]); + // Assert.Equal(reference_image_data[i], destination[i]); // } //} @@ -183,7 +189,7 @@ public void DestinationSize() // const auto&reference_image_data{ reference_file.image_data()}; // for (size_t i{ }; i != destination.size(); ++i) // { - // Assert::AreEqual(reference_image_data[i], destination[i]); + // Assert.Equal(reference_image_data[i], destination[i]); // } //} @@ -200,7 +206,7 @@ public void DestinationSize() // const auto* destination_as_bytes{ reinterpret_cast (destination.data())}; // for (size_t i{ }; i != reference_image_data.size(); ++i) // { - // Assert::AreEqual(reference_image_data[i], destination_as_bytes[i]); + // Assert.Equal(reference_image_data[i], destination_as_bytes[i]); // } //} @@ -226,45 +232,26 @@ public void DestinationSize() // assert_expect_exception(jpegls_errc::parameter_value_not_supported, [&decoder] { decoder.read_header(); }); //} - //TEST_METHOD(read_spiff_header) // NOLINT - //{ - // const vector source = create_test_spiff_header(); - // const jpegls_decoder decoder{ source, true}; - - // Assert::IsTrue(decoder.spiff_header_has_value()); - - // const auto&header{ decoder.spiff_header()}; - // Assert::AreEqual(static_cast(spiff_profile_id::none), static_cast(header.profile_id)); - // Assert::AreEqual(3, header.component_count); - // Assert::AreEqual(800U, header.height); - // Assert::AreEqual(600U, header.width); - // Assert::AreEqual(static_cast(spiff_color_space::rgb), static_cast(header.color_space)); - // Assert::AreEqual(8, header.bits_per_sample); - // Assert::AreEqual(static_cast(spiff_compression_type::jpeg_ls), - // static_cast(header.compression_type)); - // Assert::AreEqual(static_cast(spiff_resolution_units::dots_per_inch), - // static_cast(header.resolution_units)); - // Assert::AreEqual(96U, header.vertical_resolution); - // Assert::AreEqual(1024U, header.horizontal_resolution); - //} - - //TEST_METHOD(read_spiff_header_from_temporary_object) // NOLINT - //{ - // const spiff_header header{ create_decoder(create_test_spiff_header()).spiff_header()}; - - // Assert::AreEqual(static_cast(spiff_profile_id::none), static_cast(header.profile_id)); - // Assert::AreEqual(3, header.component_count); - // Assert::AreEqual(800U, header.height); - // Assert::AreEqual(600U, header.width); - // Assert::AreEqual(static_cast(spiff_color_space::rgb), static_cast(header.color_space)); - // Assert::AreEqual(8, header.bits_per_sample); - // Assert::AreEqual(static_cast(spiff_compression_type::jpeg_ls), - // static_cast(header.compression_type)); - // Assert::AreEqual(static_cast(spiff_resolution_units::dots_per_inch), - // static_cast(header.resolution_units)); - // Assert::AreEqual(96U, header.vertical_resolution); - // Assert::AreEqual(1024U, header.horizontal_resolution); - //} + [Fact (Skip = "WIP")] + public void ReadSpiffHeader() + { + var source = Util.CreateTestSpiffHeader(); + JpegLSDecoder decoder = new(source); + + Assert.NotNull(decoder.SpiffHeader); + + var header = decoder.SpiffHeader; + Assert.Equal(SpiffProfileId.None, header.ProfileId); + Assert.Equal(3, header.ComponentCount); + Assert.Equal(800, header.Height); + Assert.Equal(600, header.Width); + Assert.Equal(SpiffColorSpace.Rgb, header.ColorSpace); + Assert.Equal(8, header.BitsPerSample); + Assert.Equal(SpiffCompressionType.JpegLS, header.CompressionType); + Assert.Equal(SpiffResolutionUnit.DotsPerInch, header.ResolutionUnit); + Assert.Equal(96, header.VerticalResolution); + Assert.Equal(1024, header.HorizontalResolution); + } //TEST_METHOD(read_spiff_header_from_non_jpegls_data) // NOLINT //{ @@ -287,10 +274,10 @@ public void DestinationSize() // const frame_info&frame_info{ decoder.frame_info()}; - // Assert::AreEqual(3, frame_info.component_count); - // Assert::AreEqual(8, frame_info.bits_per_sample); - // Assert::AreEqual(256U, frame_info.height); - // Assert::AreEqual(256U, frame_info.width); + // Assert.Equal(3, frame_info.component_count); + // Assert.Equal(8, frame_info.bits_per_sample); + // Assert.Equal(256U, frame_info.height); + // Assert.Equal(256U, frame_info.width); //} //TEST_METHOD(read_header_twice) // NOLINT @@ -311,14 +298,14 @@ public void DestinationSize() // interleave_mode interleave_mode; // tie(frame_info, interleave_mode) = jpegls_decoder::decode(encoded_source, decoded_destination); - // Assert::AreEqual(3, frame_info.component_count); - // Assert::AreEqual(8, frame_info.bits_per_sample); - // Assert::AreEqual(256U, frame_info.height); - // Assert::AreEqual(256U, frame_info.width); - // Assert::AreEqual(interleave_mode::none, interleave_mode); + // Assert.Equal(3, frame_info.component_count); + // Assert.Equal(8, frame_info.bits_per_sample); + // Assert.Equal(256U, frame_info.height); + // Assert.Equal(256U, frame_info.width); + // Assert.Equal(interleave_mode::none, interleave_mode); // const size_t expected_size = static_cast(frame_info.height) * frame_info.width * frame_info.component_count; - // Assert::AreEqual(expected_size, decoded_destination.size()); + // Assert.Equal(expected_size, decoded_destination.size()); //} //TEST_METHOD(simple_decode_to_uint16_buffer) // NOLINT @@ -330,14 +317,14 @@ public void DestinationSize() // interleave_mode interleave_mode; // tie(frame_info, interleave_mode) = jpegls_decoder::decode(encoded_source, decoded_destination); - // Assert::AreEqual(3, frame_info.component_count); - // Assert::AreEqual(8, frame_info.bits_per_sample); - // Assert::AreEqual(256U, frame_info.height); - // Assert::AreEqual(256U, frame_info.width); - // Assert::AreEqual(interleave_mode::none, interleave_mode); + // Assert.Equal(3, frame_info.component_count); + // Assert.Equal(8, frame_info.bits_per_sample); + // Assert.Equal(256U, frame_info.height); + // Assert.Equal(256U, frame_info.width); + // Assert.Equal(interleave_mode::none, interleave_mode); // const size_t expected_size{ static_cast(frame_info.height) * frame_info.width * frame_info.component_count}; - // Assert::AreEqual(expected_size, decoded_destination.size() * sizeof(uint16_t)); + // Assert.Equal(expected_size, decoded_destination.size() * sizeof(uint16_t)); //} //TEST_METHOD(decode_file_with_ff_in_entropy_data) // NOLINT @@ -347,10 +334,10 @@ public void DestinationSize() // const jpegls_decoder decoder{ source, true}; // const auto&frame_info{ decoder.frame_info()}; - // Assert::AreEqual(1, frame_info.component_count); - // Assert::AreEqual(12, frame_info.bits_per_sample); - // Assert::AreEqual(1216U, frame_info.height); - // Assert::AreEqual(968U, frame_info.width); + // Assert.Equal(1, frame_info.component_count); + // Assert.Equal(12, frame_info.bits_per_sample); + // Assert.Equal(1216U, frame_info.height); + // Assert.Equal(968U, frame_info.width); // vector destination(decoder.destination_size()); @@ -365,10 +352,10 @@ public void DestinationSize() // const jpegls_decoder decoder{ source, true}; // const auto&frame_info{ decoder.frame_info()}; - // Assert::AreEqual(3, frame_info.component_count); - // Assert::AreEqual(16, frame_info.bits_per_sample); - // Assert::AreEqual(65516U, frame_info.height); - // Assert::AreEqual(1U, frame_info.width); + // Assert.Equal(3, frame_info.component_count); + // Assert.Equal(16, frame_info.bits_per_sample); + // Assert.Equal(65516U, frame_info.height); + // Assert.Equal(1U, frame_info.width); // vector destination(decoder.destination_size()); @@ -447,7 +434,7 @@ public void DestinationSize() // decoder.read_header(); - // Assert::AreEqual(static_cast(5), actual_size); + // Assert.Equal(static_cast(5), actual_size); // Assert::IsTrue(memcmp("hello", actual_data, actual_size) == 0); //} diff --git a/test/JpegStreamWriterTest.cs b/test/JpegStreamWriterTest.cs index 20c440f..5c99778 100644 --- a/test/JpegStreamWriterTest.cs +++ b/test/JpegStreamWriterTest.cs @@ -28,14 +28,16 @@ public void WriteStartOfImage() Assert.Equal((byte)JpegMarkerCode.StartOfImage, buffer[1]); } - //TEST_METHOD(write_start_of_image_in_too_small_buffer_throws) // NOLINT - //{ - // array < byte, 1 > buffer{ }; - // jpeg_stream_writer writer({ buffer.data(), buffer.size()}); + [Fact] + public void WriteStartOfImageInTooSmallBufferThrows() + { + var buffer = new byte[1]; + JpegStreamWriter writer = new JpegStreamWriter { Destination = buffer }; - // assert_expect_exception(jpegls_errc::destination_buffer_too_small, [&writer] { writer.write_start_of_image(); }); - // Assert::AreEqual(size_t{ }, writer.bytes_written()); - //} + var exception = Assert.Throws(writer.WriteStartOfImage); + Assert.False(string.IsNullOrEmpty(exception.Message)); + Assert.Equal(ErrorCode.DestinationBufferTooSmall, exception.Data[nameof(ErrorCode)]); + } [Fact] public void WriteEndOfImage() @@ -106,164 +108,174 @@ public void WriteEndOfImageEvenExtraByteNeededNotEnabled() Assert.Equal((byte)JpegMarkerCode.EndOfImage, buffer[6]); } - //TEST_METHOD(write_end_of_image_in_too_small_buffer_throws) // NOLINT - //{ - // array < byte, 1 > buffer{ }; - // jpeg_stream_writer writer({ buffer.data(), buffer.size()}); + [Fact] + public void WriteEndOfImageInTooSmallBufferThrows() + { + var buffer = new byte[1]; + JpegStreamWriter writer = new JpegStreamWriter { Destination = buffer }; - // assert_expect_exception(jpegls_errc::destination_buffer_too_small, [&writer] { writer.write_end_of_image(false); }); - // Assert::AreEqual(size_t{ }, writer.bytes_written()); - //} + var exception = Assert.Throws(() => writer.WriteEndOfImage(false)); + Assert.False(string.IsNullOrEmpty(exception.Message)); + Assert.Equal(ErrorCode.DestinationBufferTooSmall, exception.Data[nameof(ErrorCode)]); + } - //TEST_METHOD(write_spiff_segment) // NOLINT - //{ - // array < byte, 34 > buffer{ }; - // jpeg_stream_writer writer({ buffer.data(), buffer.size()}); + [Fact] + public void WriteSpiffSegment() + { + var buffer = new byte[34]; + JpegStreamWriter writer = new JpegStreamWriter { Destination = buffer }; - // constexpr spiff_header header{ - // spiff_profile_id::none, - // 3, - // 800, - // 600, - // spiff_color_space::rgb, - // 8, - // spiff_compression_type::jpeg_ls, - // spiff_resolution_units::dots_per_inch, - // 96, - // 1024}; - - // writer.write_spiff_header_segment(header); - - // Assert::AreEqual(size_t{ 34}, writer.bytes_written()); - - // Assert::AreEqual(byte{ 0xFF}, buffer[0]); - // Assert::AreEqual(static_cast(jpeg_marker_code::application_data8), buffer[1]); - - // Assert::AreEqual({ }, buffer[2]); - // Assert::AreEqual(byte{ 32}, buffer[3]); - - // // Verify SPIFF identifier string. - // Assert::AreEqual(byte{ 'S'}, buffer[4]); - // Assert::AreEqual(byte{ 'P'}, buffer[5]); - // Assert::AreEqual(byte{ 'I'}, buffer[6]); - // Assert::AreEqual(byte{ 'F'}, buffer[7]); - // Assert::AreEqual(byte{ 'F'}, buffer[8]); - // Assert::AreEqual({ }, buffer[9]); - - // // Verify version - // Assert::AreEqual(byte{ 2}, buffer[10]); - // Assert::AreEqual({ }, buffer[11]); - - // Assert::AreEqual(static_cast(header.profile_id), buffer[12]); - // Assert::AreEqual(static_cast(header.component_count), buffer[13]); - - // // Height - // Assert::AreEqual({ }, buffer[14]); - // Assert::AreEqual({ }, buffer[15]); - // Assert::AreEqual(byte{ 0x3}, buffer[16]); - // Assert::AreEqual(byte{ 0x20}, buffer[17]); - - // // Width - // Assert::AreEqual({ }, buffer[18]); - // Assert::AreEqual({ }, buffer[19]); - // Assert::AreEqual(byte{ 0x2}, buffer[20]); - // Assert::AreEqual(byte{ 0x58}, buffer[21]); - - // Assert::AreEqual(static_cast(header.color_space), buffer[22]); - // Assert::AreEqual(static_cast(header.bits_per_sample), buffer[23]); - // Assert::AreEqual(static_cast(header.compression_type), buffer[24]); - // Assert::AreEqual(static_cast(header.resolution_units), buffer[25]); - - // // vertical_resolution - // Assert::AreEqual({ }, buffer[26]); - // Assert::AreEqual({ }, buffer[27]); - // Assert::AreEqual({ }, buffer[28]); - // Assert::AreEqual(byte{ 96}, buffer[29]); - - // // header.horizontal_resolution = 1024 - // Assert::AreEqual({ }, buffer[30]); - // Assert::AreEqual({ }, buffer[31]); - // Assert::AreEqual(byte{ 4}, buffer[32]); - // Assert::AreEqual(byte{ }, buffer[33]); - //} + var header = new SpiffHeader + { + ProfileId = SpiffProfileId.None, + ComponentCount = 3, + Height = 800, + Width = 600, + ColorSpace = SpiffColorSpace.Rgb, + BitsPerSample = 8, + CompressionType = SpiffCompressionType.JpegLS, + ResolutionUnit = SpiffResolutionUnit.DotsPerInch, + VerticalResolution = 96, + HorizontalResolution = 1024 + }; - //TEST_METHOD(write_spiff_segment_in_too_small_buffer_throws) // NOLINT - //{ - // array < byte, 33 > buffer{ }; - // jpeg_stream_writer writer({ buffer.data(), buffer.size()}); + writer.WriteSpiffHeaderSegment(header); - // spiff_header header{ - // spiff_profile_id::none, - // 3, - // 800, - // 600, - // spiff_color_space::rgb, - // 8, - // spiff_compression_type::jpeg_ls, - // spiff_resolution_units::dots_per_inch, - // 96, - // 1024}; - - // assert_expect_exception(jpegls_errc::destination_buffer_too_small, - // [&writer, &header] { writer.write_spiff_header_segment(header); }); - // Assert::AreEqual(size_t{ }, writer.bytes_written()); - //} + Assert.Equal(34, writer.BytesWritten); - //TEST_METHOD(write_spiff_end_of_directory_segment) // NOLINT - //{ - // array < byte, 10 > buffer{ }; - // jpeg_stream_writer writer({ buffer.data(), buffer.size()}); + Assert.Equal(0xFF, buffer[0]); + Assert.Equal((byte)JpegMarkerCode.ApplicationData8, buffer[1]); - // writer.write_spiff_end_of_directory_entry(); + Assert.Equal(0, buffer[2]); + Assert.Equal(32, buffer[3]); - // Assert::AreEqual(size_t{ 10}, writer.bytes_written()); + // Verify SPIFF identifier string. + Assert.Equal((byte)'S', buffer[4]); + Assert.Equal((byte)'P', buffer[5]); + Assert.Equal((byte)'I', buffer[6]); + Assert.Equal((byte)'F', buffer[7]); + Assert.Equal((byte)'F', buffer[8]); + Assert.Equal(0, buffer[9]); - // // Verify Entry Magic Number (EMN) - // Assert::AreEqual(byte{ 0xFF}, buffer[0]); - // Assert::AreEqual(static_cast(jpeg_marker_code::application_data8), buffer[1]); + // Verify version + Assert.Equal(2, buffer[10]); + Assert.Equal(0, buffer[11]); - // // Verify EOD Entry Length (EOD = End Of Directory) - // Assert::AreEqual(byte{ }, buffer[2]); - // Assert::AreEqual(byte{ 8}, buffer[3]); + Assert.Equal((byte)header.ProfileId, buffer[12]); + Assert.Equal((byte)header.ComponentCount, buffer[13]); - // // Verify EOD Tag - // Assert::AreEqual({ }, buffer[4]); - // Assert::AreEqual(byte{ }, buffer[5]); - // Assert::AreEqual(byte{ }, buffer[6]); - // Assert::AreEqual(byte{ 1}, buffer[7]); + // Height + Assert.Equal(0, buffer[14]); + Assert.Equal(0, buffer[15]); + Assert.Equal(0x3, buffer[16]); + Assert.Equal(0x20, buffer[17]); - // // Verify embedded SOI tag - // Assert::AreEqual(byte{ 0xFF}, buffer[8]); - // Assert::AreEqual(static_cast(jpeg_marker_code::start_of_image), buffer[9]); - //} + // Width + Assert.Equal(0, buffer[18]); + Assert.Equal(0, buffer[19]); + Assert.Equal(0x2, buffer[20]); + Assert.Equal(0x58, buffer[21]); + + Assert.Equal((byte)header.ColorSpace, buffer[22]); + Assert.Equal((byte)header.BitsPerSample, buffer[23]); + Assert.Equal((byte)header.CompressionType, buffer[24]); + Assert.Equal((byte)header.ResolutionUnit, buffer[25]); + + // vertical_resolution + Assert.Equal(0, buffer[26]); + Assert.Equal(0, buffer[27]); + Assert.Equal(0, buffer[28]); + Assert.Equal(96, buffer[29]); + + // header.horizontal_resolution = 1024 + Assert.Equal(0, buffer[30]); + Assert.Equal(0, buffer[31]); + Assert.Equal(4, buffer[32]); + Assert.Equal(0, buffer[33]); + } - //TEST_METHOD(write_spiff_directory_entry) // NOLINT - //{ - // array < byte, 10 > buffer{ }; - // jpeg_stream_writer writer{ { buffer.data(), buffer.size()} }; + [Fact] + public void WriteSpiffSegmentInTooSmallBufferThrows() + { + var buffer = new byte[33]; + JpegStreamWriter writer = new JpegStreamWriter { Destination = buffer }; - // constexpr array data{ byte{ 0x77}, byte{ 0x66} }; + var header = new SpiffHeader + { + ProfileId = SpiffProfileId.None, + ComponentCount = 3, + Height = 800, + Width = 600, + ColorSpace = SpiffColorSpace.Rgb, + BitsPerSample = 8, + CompressionType = SpiffCompressionType.JpegLS, + ResolutionUnit = SpiffResolutionUnit.DotsPerInch, + VerticalResolution = 96, + HorizontalResolution = 1024 + }; + + var exception = Assert.Throws(() => writer.WriteSpiffHeaderSegment(header)); + Assert.False(string.IsNullOrEmpty(exception.Message)); + Assert.Equal(ErrorCode.DestinationBufferTooSmall, exception.Data[nameof(ErrorCode)]); + } - // writer.write_spiff_directory_entry(2, data); + [Fact] + public void WriteSpiffEndOfDirectorySegment() + { + var buffer = new byte[10]; + JpegStreamWriter writer = new JpegStreamWriter { Destination = buffer }; - // // Verify Entry Magic Number (EMN) - // Assert::AreEqual(byte{ 0xFF}, buffer[0]); - // Assert::AreEqual(static_cast(jpeg_marker_code::application_data8), buffer[1]); + writer.WriteSpiffEndOfDirectoryEntry(); - // // Verify Entry Length - // Assert::AreEqual({ }, buffer[2]); - // Assert::AreEqual(byte{ 8}, buffer[3]); + Assert.Equal(10, writer.BytesWritten); - // // Verify Entry Tag - // Assert::AreEqual({ }, buffer[4]); - // Assert::AreEqual({ }, buffer[5]); - // Assert::AreEqual({ }, buffer[6]); - // Assert::AreEqual(byte{ 2}, buffer[7]); + // Verify Entry Magic Number (EMN) + Assert.Equal(0xFF, buffer[0]); + Assert.Equal((byte)JpegMarkerCode.ApplicationData8, buffer[1]); - // // Verify embedded data - // Assert::AreEqual(data[0], buffer[8]); - // Assert::AreEqual(data[1], buffer[9]); - //} + // Verify EOD Entry Length (EOD = End Of Directory) + Assert.Equal(0, buffer[2]); + Assert.Equal(8, buffer[3]); + + // Verify EOD Tag + Assert.Equal(0, buffer[4]); + Assert.Equal(0, buffer[5]); + Assert.Equal(0, buffer[6]); + Assert.Equal(1, buffer[7]); + + // Verify embedded SOI tag + Assert.Equal(0xFF, buffer[8]); + Assert.Equal((byte)JpegMarkerCode.StartOfImage, buffer[9]); + } + + [Fact] + public void WriteSpiffDirectoryEntry() + { + var buffer = new byte[10]; + JpegStreamWriter writer = new JpegStreamWriter { Destination = buffer }; + + Span data = [0x77, 0x66]; + + writer.WriteSpiffDirectoryEntry(2, data); + + // Verify Entry Magic Number (EMN) + Assert.Equal(0xFF, buffer[0]); + Assert.Equal((byte)JpegMarkerCode.ApplicationData8, buffer[1]); + + // Verify Entry Length + Assert.Equal(0, buffer[2]); + Assert.Equal(8, buffer[3]); + + // Verify Entry Tag + Assert.Equal(0, buffer[4]); + Assert.Equal(0, buffer[5]); + Assert.Equal(0, buffer[6]); + Assert.Equal(2, buffer[7]); + + // Verify embedded data + Assert.Equal(data[0], buffer[8]); + Assert.Equal(data[1], buffer[9]); + } [Fact] public void TestWriteStartOfFrameSegment() @@ -304,44 +316,44 @@ public void TestWriteStartOfFrameSegment() Assert.Equal(0, buffer[18]); } - //TEST_METHOD(write_start_of_frame_segment_large_image) // NOLINT - //{ - // constexpr int bits_per_sample{ 8}; - // constexpr int component_count{ 3}; + [Fact] + public void WriteStartOfFrameSegmentLargeImage() + { + const int bitsPerSample = 8; + const int componentCount = 3; - // array < byte, 19 > buffer{ }; - // jpeg_stream_writer writer({ buffer.data(), buffer.size()}); + var buffer = new byte[19]; + JpegStreamWriter writer = new JpegStreamWriter { Destination = buffer }; - // const bool oversized_image{ - // writer.write_start_of_frame_segment( - // { 100, numeric_limits < uint16_t >::max() + 1U, bits_per_sample, component_count})}; - - // Assert::IsTrue(oversized_image); - // Assert::AreEqual(size_t{ 19}, writer.bytes_written()); - - // Assert::AreEqual(byte{ 0xFF}, buffer[0]); - // Assert::AreEqual(byte{ 0xF7}, buffer[1]); // JPEG_SOF_55 - // Assert::AreEqual(byte{ }, buffer[2]); // 6 + (3 * 3) + 2 (in big endian) - // Assert::AreEqual(byte{ 17}, buffer[3]); // 6 + (3 * 3) + 2 (in big endian) - // Assert::AreEqual(static_cast(bits_per_sample), buffer[4]); - // Assert::AreEqual(byte{ }, buffer[5]); // height (in big endian) - // Assert::AreEqual(byte{ }, buffer[6]); // height (in big endian) - // Assert::AreEqual(byte{ }, buffer[7]); // width (in big endian) - // Assert::AreEqual(byte{ }, buffer[8]); // width (in big endian) - // Assert::AreEqual(static_cast(component_count), buffer[9]); - - // Assert::AreEqual(byte{ 1}, buffer[10]); - // Assert::AreEqual(byte{ 0x11}, buffer[11]); - // Assert::AreEqual(byte{ }, buffer[12]); - - // Assert::AreEqual(byte{ 2}, buffer[13]); - // Assert::AreEqual(byte{ 0x11}, buffer[14]); - // Assert::AreEqual(byte{ }, buffer[15]); - - // Assert::AreEqual(byte{ 3}, buffer[16]); - // Assert::AreEqual(byte{ 0x11}, buffer[17]); - // Assert::AreEqual(byte{ }, buffer[18]); - //} + bool oversizedImage = + writer.WriteStartOfFrameSegment(new FrameInfo(100, ushort.MaxValue + 1, bitsPerSample, componentCount)); + + Assert.True(oversizedImage); + Assert.Equal(19, writer.BytesWritten); + + Assert.Equal(0xFF, buffer[0]); + Assert.Equal(0xF7, buffer[1]); // JPEG_SOF_55 + Assert.Equal(0, buffer[2]); // 6 + (3 * 3) + 2 (in big endian) + Assert.Equal(17, buffer[3]); // 6 + (3 * 3) + 2 (in big endian) + Assert.Equal(bitsPerSample, buffer[4]); + Assert.Equal(0, buffer[5]); // height (in big endian) + Assert.Equal(0, buffer[6]); // height (in big endian) + Assert.Equal(0, buffer[7]); // width (in big endian) + Assert.Equal(0, buffer[8]); // width (in big endian) + Assert.Equal(componentCount, buffer[9]); + + Assert.Equal(1, buffer[10]); + Assert.Equal(0x11, buffer[11]); + Assert.Equal(0, buffer[12]); + + Assert.Equal(2, buffer[13]); + Assert.Equal(0x11, buffer[14]); + Assert.Equal(0, buffer[15]); + + Assert.Equal(3, buffer[16]); + Assert.Equal(0x11, buffer[17]); + Assert.Equal(0, buffer[18]); + } [Fact] public void WriteStartOfFrameMarkerSegmentWithLowBoundaryValues() @@ -358,99 +370,101 @@ public void WriteStartOfFrameMarkerSegmentWithLowBoundaryValues() Assert.Equal(componentCount, buffer[9]); } - //TEST_METHOD(write_start_of_frame_marker_segment_with_high_boundary_values_and_serialize) // NOLINT - //{ - // array < byte, 775 > buffer{ }; - // jpeg_stream_writer writer({ buffer.data(), buffer.size()}); - // writer.write_start_of_frame_segment( - // { numeric_limits < uint16_t >::max(), numeric_limits < uint16_t >::max(), 16, numeric_limits < uint8_t >::max()}); - - // Assert::AreEqual(buffer.size(), writer.bytes_written()); - // Assert::AreEqual(byte{ 16}, buffer[4]); - // Assert::AreEqual(numeric_limits < uint8_t >::max(), to_integer(buffer[9])); - // Assert::AreEqual(numeric_limits < uint8_t >::max(), - // to_integer(buffer[buffer.size() - 3])); // Last component index. - //} + [Fact] + public void WriteStartOfFrameMarkerSegmentWithHighBoundaryValuesAndSerialize() + { + var buffer = new byte[775]; + JpegStreamWriter writer = new JpegStreamWriter { Destination = buffer }; - //TEST_METHOD(write_color_transform_segment) // NOLINT - //{ - // constexpr color_transformation transformation = color_transformation::hp1; + writer.WriteStartOfFrameSegment(new FrameInfo(ushort.MaxValue, ushort.MaxValue, 16, byte.MaxValue)); - // array < byte, 9 > buffer{ }; - // jpeg_stream_writer writer({ buffer.data(), buffer.size()}); + Assert.Equal(buffer.Length, writer.BytesWritten); + Assert.Equal(16, buffer[4]); + Assert.Equal(byte.MaxValue, buffer[9]); + Assert.Equal(byte.MaxValue, buffer[^3]); // Last component index. + } - // writer.write_color_transform_segment(transformation); - // Assert::AreEqual(buffer.size(), writer.bytes_written()); + [Fact] + public void WriteColorTransformSegment() + { + const ColorTransformation transformation = ColorTransformation.HP1; + var buffer = new byte[9]; + JpegStreamWriter writer = new JpegStreamWriter { Destination = buffer }; - // // Verify mrfx identifier string. - // Assert::AreEqual(byte{ 'm'}, buffer[4]); - // Assert::AreEqual(byte{ 'r'}, buffer[5]); - // Assert::AreEqual(byte{ 'f'}, buffer[6]); - // Assert::AreEqual(byte{ 'x'}, buffer[7]); + writer.WriteColorTransformSegment(transformation); + Assert.Equal(buffer.Length, writer.BytesWritten); - // Assert::AreEqual(static_cast(transformation), buffer[8]); - //} + // Verify mrfx identifier string. + Assert.Equal((byte)'m', buffer[4]); + Assert.Equal((byte)'r', buffer[5]); + Assert.Equal((byte)'f', buffer[6]); + Assert.Equal((byte)'x', buffer[7]); - //TEST_METHOD(write_jpegls_extended_parameters_marker_and_serialize) // NOLINT - //{ - // constexpr jpegls_pc_parameters presets{ 2, 1, 2, 3, 7}; + Assert.Equal((byte)transformation, buffer[8]); + } - // array < byte, 15 > buffer{ }; - // jpeg_stream_writer writer({ buffer.data(), buffer.size()}); + [Fact] + public void WriteJpegLSExtendedParametersMarkerAndSerialize() + { + var parameters = new JpegLSPresetCodingParameters(2, 1, 2, 3, 7); + var buffer = new byte[15]; + JpegStreamWriter writer = new JpegStreamWriter { Destination = buffer }; - // writer.write_jpegls_preset_parameters_segment(presets); - // Assert::AreEqual(buffer.size(), writer.bytes_written()); + writer.WriteJpegLSPresetParametersSegment(parameters); - // // Parameter ID. - // Assert::AreEqual(byte{ 0x1}, buffer[4]); + Assert.Equal(buffer.Length, writer.BytesWritten); - // // MaximumSampleValue - // Assert::AreEqual({ }, buffer[5]); - // Assert::AreEqual(byte{ 2}, buffer[6]); + // Parameter ID. + Assert.Equal(0x1, buffer[4]); - // // Threshold1 - // Assert::AreEqual({ }, buffer[7]); - // Assert::AreEqual(byte{ 1}, buffer[8]); + // MaximumSampleValue + Assert.Equal(0, buffer[5]); + Assert.Equal(2, buffer[6]); - // // Threshold2 - // Assert::AreEqual({ }, buffer[9]); - // Assert::AreEqual(byte{ 2}, buffer[10]); + // Threshold1 + Assert.Equal(0, buffer[7]); + Assert.Equal(1, buffer[8]); - // // Threshold3 - // Assert::AreEqual({ }, buffer[11]); - // Assert::AreEqual(byte{ 3}, buffer[12]); + // Threshold2 + Assert.Equal(0, buffer[9]); + Assert.Equal(2, buffer[10]); - // // ResetValue - // Assert::AreEqual({ }, buffer[13]); - // Assert::AreEqual(byte{ 7}, buffer[14]); - //} + // Threshold3 + Assert.Equal(0, buffer[11]); + Assert.Equal(3, buffer[12]); - //TEST_METHOD(write_jpegls_preset_parameters_segment_for_oversized_image_dimensions) // NOLINT - //{ - // array < byte, 14 > buffer{ }; - // jpeg_stream_writer writer({ buffer.data(), buffer.size()}); + // ResetValue + Assert.Equal(0, buffer[13]); + Assert.Equal(7, buffer[14]); + } + + [Fact] + public void WriteJpegLSPresetParametersSegmentForOversizedImageDimensions() + { + var buffer = new byte[14]; + JpegStreamWriter writer = new JpegStreamWriter { Destination = buffer }; - // writer.write_jpegls_preset_parameters_segment(100, numeric_limits < uint32_t >::max()); - // Assert::AreEqual(buffer.size(), writer.bytes_written()); + writer.WriteJpegLSPresetParametersSegment(100, int.MaxValue); + Assert.Equal(buffer.Length, writer.BytesWritten); - // // Parameter ID. - // Assert::AreEqual(byte{ 0x4}, buffer[4]); + // Parameter ID. + Assert.Equal(0x4, buffer[4]); - // // Wxy - // Assert::AreEqual(byte{ 4}, buffer[5]); + // Wxy + Assert.Equal(4, buffer[5]); - // // Height (in big endian) - // Assert::AreEqual({ }, buffer[6]); - // Assert::AreEqual({ }, buffer[7]); - // Assert::AreEqual({ }, buffer[8]); - // Assert::AreEqual(byte{ 100}, buffer[9]); + // Height (in big endian) + Assert.Equal(0, buffer[6]); + Assert.Equal(0, buffer[7]); + Assert.Equal(0, buffer[8]); + Assert.Equal(100, buffer[9]); - // // Width (in big endian) - // Assert::AreEqual(byte{ 255}, buffer[10]); - // Assert::AreEqual(byte{ 255}, buffer[11]); - // Assert::AreEqual(byte{ 255}, buffer[12]); - // Assert::AreEqual(byte{ 255}, buffer[13]); - //} + // Width (in big endian) + Assert.Equal(127, buffer[10]); + Assert.Equal(255, buffer[11]); + Assert.Equal(255, buffer[12]); + Assert.Equal(255, buffer[13]); + } [Fact] public void WriteStartOfScanMarker() @@ -469,19 +483,21 @@ public void WriteStartOfScanMarker() Assert.Equal(0, buffer[9]); // transformation. } - //TEST_METHOD(rewind) // NOLINT - //{ - // array < byte, 10 > buffer{ }; - // jpeg_stream_writer writer({ buffer.data(), buffer.size()}); + [Fact] + public void Rewind() + { + var buffer = new byte[10]; + JpegStreamWriter writer = new JpegStreamWriter { Destination = buffer }; + writer.WriteStartOfScanSegment(1, 2, InterleaveMode.None); - // writer.write_start_of_scan_segment(1, 2, interleave_mode::none); - // writer.rewind(); - // buffer[4] = { }; - // writer.write_start_of_scan_segment(1, 2, interleave_mode::none); + writer.Rewind(); - // Assert::AreEqual(buffer.size(), writer.bytes_written()); - // Assert::AreEqual(byte{ 1}, buffer[4]); // component count. - //} + buffer[4] = 0; + writer.WriteStartOfScanSegment(1, 2, InterleaveMode.None); + + Assert.Equal(buffer.Length, writer.BytesWritten); + Assert.Equal(1, buffer[4]); // component count. + } //TEST_METHOD(write_minimal_table) // NOLINT //{ @@ -491,15 +507,15 @@ public void WriteStartOfScanMarker() // constexpr array table_data{ byte{ 77} }; // writer.write_jpegls_preset_parameters_segment(100, 1, table_data); - // Assert::AreEqual(buffer.size(), writer.bytes_written()); - // Assert::AreEqual(byte{ 0xFF}, buffer[0]); - // Assert::AreEqual(byte{ 0xF8}, buffer[1]); // LSE - // Assert::AreEqual(byte{ 0}, buffer[2]); - // Assert::AreEqual(byte{ 6}, buffer[3]); - // Assert::AreEqual(byte{ 2}, buffer[4]); // type = table - // Assert::AreEqual(byte{ 100}, buffer[5]); // table ID - // Assert::AreEqual(byte{ 1}, buffer[6]); // size of entry - // Assert::AreEqual(byte{ 77}, buffer[7]); // table content + // Assert.Equal(buffer.size(), writer.bytes_written()); + // Assert.Equal(byte{ 0xFF}, buffer[0]); + // Assert.Equal(byte{ 0xF8}, buffer[1]); // LSE + // Assert.Equal(byte{ 0}, buffer[2]); + // Assert.Equal(byte{ 6}, buffer[3]); + // Assert.Equal(byte{ 2}, buffer[4]); // type = table + // Assert.Equal(byte{ 100}, buffer[5]); // table ID + // Assert.Equal(byte{ 1}, buffer[6]); // size of entry + // Assert.Equal(byte{ 77}, buffer[7]); // table content //} //TEST_METHOD(write_table_max_entry_size) // NOLINT @@ -510,15 +526,15 @@ public void WriteStartOfScanMarker() // constexpr array table_data{ }; // writer.write_jpegls_preset_parameters_segment(100, 255, table_data); - // Assert::AreEqual(buffer.size(), writer.bytes_written()); - // Assert::AreEqual(byte{ 0xFF}, buffer[0]); - // Assert::AreEqual(byte{ 0xF8}, buffer[1]); // LSE - // Assert::AreEqual(byte{ 1}, buffer[2]); - // Assert::AreEqual(byte{ 4}, buffer[3]); - // Assert::AreEqual(byte{ 2}, buffer[4]); // type = table - // Assert::AreEqual(byte{ 100}, buffer[5]); // table ID - // Assert::AreEqual(byte{ 255}, buffer[6]); // size of entry - // Assert::AreEqual(byte{ 0}, buffer[7]); // table content + // Assert.Equal(buffer.size(), writer.bytes_written()); + // Assert.Equal(byte{ 0xFF}, buffer[0]); + // Assert.Equal(byte{ 0xF8}, buffer[1]); // LSE + // Assert.Equal(byte{ 1}, buffer[2]); + // Assert.Equal(byte{ 4}, buffer[3]); + // Assert.Equal(byte{ 2}, buffer[4]); // type = table + // Assert.Equal(byte{ 100}, buffer[5]); // table ID + // Assert.Equal(byte{ 255}, buffer[6]); // size of entry + // Assert.Equal(byte{ 0}, buffer[7]); // table content //} //TEST_METHOD(write_table_fits_in_single_segment) // NOLINT @@ -530,15 +546,15 @@ public void WriteStartOfScanMarker() // std::vector table_data(std::numeric_limits::max() -5); // writer.write_jpegls_preset_parameters_segment(255, 1, { table_data.data(), table_data.size()}); - // Assert::AreEqual(buffer.size(), writer.bytes_written()); - // Assert::AreEqual(byte{ 0xFF}, buffer[0]); - // Assert::AreEqual(byte{ 0xF8}, buffer[1]); // LSE - // Assert::AreEqual(byte{ 255}, buffer[2]); - // Assert::AreEqual(byte{ 255}, buffer[3]); - // Assert::AreEqual(byte{ 2}, buffer[4]); // type = table - // Assert::AreEqual(byte{ 255}, buffer[5]); // table ID - // Assert::AreEqual(byte{ 1}, buffer[6]); // size of entry - // Assert::AreEqual(byte{ 0}, buffer[7]); // table content (first entry) + // Assert.Equal(buffer.size(), writer.bytes_written()); + // Assert.Equal(byte{ 0xFF}, buffer[0]); + // Assert.Equal(byte{ 0xF8}, buffer[1]); // LSE + // Assert.Equal(byte{ 255}, buffer[2]); + // Assert.Equal(byte{ 255}, buffer[3]); + // Assert.Equal(byte{ 2}, buffer[4]); // type = table + // Assert.Equal(byte{ 255}, buffer[5]); // table ID + // Assert.Equal(byte{ 1}, buffer[6]); // size of entry + // Assert.Equal(byte{ 0}, buffer[7]); // table content (first entry) //} //TEST_METHOD(write_table_that_requires_two_segment) // NOLINT @@ -550,25 +566,25 @@ public void WriteStartOfScanMarker() // std::vector table_data(static_cast(std::numeric_limits::max()) -5 + 1); // writer.write_jpegls_preset_parameters_segment(255, 1, { table_data.data(), table_data.size()}); - // Assert::AreEqual(buffer.size(), writer.bytes_written()); - // Assert::AreEqual(byte{ 0xFF}, buffer[0]); - // Assert::AreEqual(byte{ 0xF8}, buffer[1]); // LSE - // Assert::AreEqual(byte{ 255}, buffer[2]); - // Assert::AreEqual(byte{ 255}, buffer[3]); - // Assert::AreEqual(byte{ 2}, buffer[4]); // type = table - // Assert::AreEqual(byte{ 255}, buffer[5]); // table ID - // Assert::AreEqual(byte{ 1}, buffer[6]); // size of entry - // Assert::AreEqual(byte{ 0}, buffer[7]); // table content (first entry) + // Assert.Equal(buffer.size(), writer.bytes_written()); + // Assert.Equal(byte{ 0xFF}, buffer[0]); + // Assert.Equal(byte{ 0xF8}, buffer[1]); // LSE + // Assert.Equal(byte{ 255}, buffer[2]); + // Assert.Equal(byte{ 255}, buffer[3]); + // Assert.Equal(byte{ 2}, buffer[4]); // type = table + // Assert.Equal(byte{ 255}, buffer[5]); // table ID + // Assert.Equal(byte{ 1}, buffer[6]); // size of entry + // Assert.Equal(byte{ 0}, buffer[7]); // table content (first entry) // // Validate second segment. - // Assert::AreEqual(byte{ 0xFF}, buffer[65537]); - // Assert::AreEqual(byte{ 0xF8}, buffer[65538]); // LSE - // Assert::AreEqual(byte{ }, buffer[65539]); - // Assert::AreEqual(byte{ 6}, buffer[65540]); - // Assert::AreEqual(byte{ 3}, buffer[65541]); // type = table - // Assert::AreEqual(byte{ 255}, buffer[65542]); // table ID - // Assert::AreEqual(byte{ 1}, buffer[65543]); // size of entry - // Assert::AreEqual(byte{ 0}, buffer[65544]); // table content (last entry) + // Assert.Equal(byte{ 0xFF}, buffer[65537]); + // Assert.Equal(byte{ 0xF8}, buffer[65538]); // LSE + // Assert.Equal(byte{ }, buffer[65539]); + // Assert.Equal(byte{ 6}, buffer[65540]); + // Assert.Equal(byte{ 3}, buffer[65541]); // type = table + // Assert.Equal(byte{ 255}, buffer[65542]); // table ID + // Assert.Equal(byte{ 1}, buffer[65543]); // size of entry + // Assert.Equal(byte{ 0}, buffer[65544]); // table content (last entry) //} } diff --git a/test/Util.cs b/test/Util.cs index f5faed9..5d70aec 100644 --- a/test/Util.cs +++ b/test/Util.cs @@ -3,6 +3,7 @@ using System.Reflection; using System.Runtime.InteropServices; + using Xunit; namespace CharLS.JpegLS.Test; @@ -54,16 +55,89 @@ internal static PortableAnymapFile ReadAnymapReferenceFile(string filename, Inte { referenceFile.ImageData = TripletToPlanar(referenceFile.ImageData, referenceFile.Width, referenceFile.Height); } - return referenceFile; } + byte[] create_test_spiff_header(int highVersion, int lowVersion, bool end_of_directory, int component_count) + { + List buffer = new(); + + buffer.Add(0xFF); + buffer.Add(0xD8); // SOI. + + buffer.Add(0xFF); + buffer.Add(0xE8); // ApplicationData8 + buffer.Add(0); + buffer.Add(32); + + //// SPIFF identifier string. + //buffer.push_back(byte{ 'S'}); + //buffer.push_back(byte{ 'P'}); + //buffer.push_back(byte{ 'I'}); + //buffer.push_back(byte{ 'F'}); + //buffer.push_back(byte{ 'F'}); + //buffer.push_back({ }); + + // Version + buffer.Add((byte)highVersion); + buffer.Add((byte)lowVersion); + + //buffer.push_back({ }); // profile id + //buffer.push_back(byte{ component_count}); + + //// Height + //buffer.push_back({ }); + //buffer.push_back({ }); + //buffer.push_back(byte{ 0x3}); + //buffer.push_back(byte{ 0x20}); + + //// Width + //buffer.push_back({ }); + //buffer.push_back({ }); + //buffer.push_back(byte{ 0x2}); + //buffer.push_back(byte{ 0x58}); + + //buffer.push_back(byte{ 10}); // color space + //buffer.push_back(byte{ 8}); // bits per sample + //buffer.push_back(byte{ 6}); // compression type, 6 = JPEG-LS + //buffer.push_back(byte{ 1}); // resolution units + + //// vertical_resolution + //buffer.push_back({ }); + //buffer.push_back({ }); + //buffer.push_back({ }); + //buffer.push_back(byte{ 96}); + + //// header.horizontal_resolution = 1024 + //buffer.push_back({ }); + //buffer.push_back({ }); + //buffer.push_back(byte{ 4}); + //buffer.push_back({ }); + + //const size_t spiff_header_size{buffer.size()}; + //buffer.resize(buffer.size() + 100); + //jpeg_stream_writer writer; + //writer.destination({ buffer.data() + spiff_header_size, buffer.size() - spiff_header_size}); + + //if (end_of_directory) + //{ + // writer.write_spiff_end_of_directory_entry(); + //} + + //writer.write_start_of_frame_segment({ 600, 800, 8, 3}); + //writer.write_start_of_scan_segment(1, 0, interleave_mode::none); + + return buffer.ToArray(); + } + + internal static void TestCompliance(byte[] encodedSource, byte[] uncompressedSource, bool checkEncode) { JpegLSDecoder decoder = new(encodedSource); if (checkEncode) { + // TODO: enable! //Assert::IsTrue(verify_encoded_bytes(uncompressed_source, encoded_source)); }