|
10 | 10 | using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
11 | 11 | using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; |
12 | 12 | using Xunit.Abstractions; |
| 13 | +using static SixLabors.ImageSharp.Tests.TestImages.Jpeg; |
13 | 14 |
|
14 | 15 | // ReSharper disable InconsistentNaming |
15 | 16 | namespace SixLabors.ImageSharp.Tests.Formats.Jpg; |
@@ -300,6 +301,124 @@ public void Issue2136_DecodeWorks<TPixel>(TestImageProvider<TPixel> provider) |
300 | 301 | image.CompareToOriginal(provider); |
301 | 302 | } |
302 | 303 |
|
| 304 | + /// <summary> |
| 305 | + /// Uses the JPEG defaults when an arithmetic conditioning table is not declared. |
| 306 | + /// </summary> |
| 307 | + /// <param name="originalFile">The input with explicit conditioning tables.</param> |
| 308 | + /// <param name="defaultFile">The input that omits default conditioning tables.</param> |
| 309 | + [Theory] |
| 310 | + [InlineData(MarkerDirectory + "arithmetic.jpg", MarkerDirectory + "arithmetic-defaults.jpg")] |
| 311 | + [InlineData(MarkerDirectory + "arithmetic-progressive.jpg", MarkerDirectory + "arithmetic-progressive-defaults.jpg")] |
| 312 | + [InlineData(MarkerDirectory + "arithmetic-progressive.jpg", MarkerDirectory + "arithmetic-progressive-default-ac.jpg")] |
| 313 | + public void Decode_ArithmeticConditioningDefaults_MatchExplicitTables(string originalFile, string defaultFile) |
| 314 | + { |
| 315 | + // These stored files contain identical coded scans. Libjpeg produces identical pixels with or without |
| 316 | + // the default DAC entries. Omission is valid JPEG syntax and does not require image-data recovery. |
| 317 | + using Image<Rgba32> expected = Image.Load<Rgba32>(TestFile.Create(originalFile).Bytes); |
| 318 | + byte[] data = TestFile.Create(defaultFile).Bytes; |
| 319 | + |
| 320 | + foreach (SegmentIntegrityHandling integrity in Enum.GetValues<SegmentIntegrityHandling>()) |
| 321 | + { |
| 322 | + DecoderOptions options = new() { SegmentIntegrityHandling = integrity }; |
| 323 | + using Image<Rgba32> actual = Image.Load<Rgba32>(options, data); |
| 324 | + |
| 325 | + ImageComparer.Exact.VerifySimilarity(expected, actual); |
| 326 | + } |
| 327 | + } |
| 328 | + |
| 329 | + /// <summary> |
| 330 | + /// Checks restart recovery against clean stored files whose pixels also match in libjpeg. |
| 331 | + /// </summary> |
| 332 | + /// <param name="originalFile">The clean JPEG input.</param> |
| 333 | + /// <param name="malformedFile">The input with extra markers before restart markers.</param> |
| 334 | + [Theory] |
| 335 | + [InlineData(MarkerDirectory + "baseline.jpg", MarkerDirectory + "baseline-reserved-12.jpg")] |
| 336 | + [InlineData(MarkerDirectory + "baseline.jpg", MarkerDirectory + "baseline-tem.jpg")] |
| 337 | + [InlineData(MarkerDirectory + "baseline.jpg", MarkerDirectory + "baseline-reserved-bf.jpg")] |
| 338 | + [InlineData(MarkerDirectory + "baseline.jpg", MarkerDirectory + "baseline-reserved-chain.jpg")] |
| 339 | + [InlineData(MarkerDirectory + "noninterleaved.jpg", MarkerDirectory + "noninterleaved-reserved-12.jpg")] |
| 340 | + [InlineData(MarkerDirectory + "progressive.jpg", MarkerDirectory + "progressive-reserved-12.jpg")] |
| 341 | + [InlineData(MarkerDirectory + "arithmetic.jpg", MarkerDirectory + "arithmetic-reserved-12.jpg")] |
| 342 | + [InlineData(MarkerDirectory + "arithmetic-progressive.jpg", MarkerDirectory + "arithmetic-progressive-reserved-12.jpg")] |
| 343 | + public void Decode_ReservedMarkerBeforeRestartMarkers_RecoversWithIgnoreImageData(string originalFile, string malformedFile) |
| 344 | + { |
| 345 | + // These stored files have complete restart intervals. Libjpeg warns about the extra markers, |
| 346 | + // then finds the restart marker and produces the same pixels as the clean file. |
| 347 | + // The arithmetic inputs declare their conditioning tables to isolate restart recovery. |
| 348 | + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreImageData }; |
| 349 | + using Image<Rgba32> expected = Image.Load<Rgba32>(TestFile.Create(originalFile).Bytes); |
| 350 | + using Image<Rgba32> actual = Image.Load<Rgba32>(options, TestFile.Create(malformedFile).Bytes); |
| 351 | + |
| 352 | + ImageComparer.Exact.VerifySimilarity(expected, actual); |
| 353 | + } |
| 354 | + |
| 355 | + /// <summary> |
| 356 | + /// Rejects image-data errors when the selected policy permits only ancillary recovery or no recovery. |
| 357 | + /// </summary> |
| 358 | + /// <param name="fileName">The input with reserved markers before restart markers.</param> |
| 359 | + [Theory] |
| 360 | + [InlineData(MarkerDirectory + "baseline-reserved-12.jpg")] |
| 361 | + [InlineData(MarkerDirectory + "noninterleaved-reserved-12.jpg")] |
| 362 | + [InlineData(MarkerDirectory + "progressive-reserved-12.jpg")] |
| 363 | + [InlineData(MarkerDirectory + "arithmetic-reserved-12.jpg")] |
| 364 | + [InlineData(MarkerDirectory + "arithmetic-progressive-reserved-12.jpg")] |
| 365 | + public void Decode_ReservedMarkerBeforeRestartMarkers_RespectsIntegrity(string fileName) |
| 366 | + { |
| 367 | + byte[] data = TestFile.Create(fileName).Bytes; |
| 368 | + DecoderOptions strict = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; |
| 369 | + DecoderOptions ancillary = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreAncillary }; |
| 370 | + |
| 371 | + Assert.Throws<InvalidImageContentException>(() => |
| 372 | + { |
| 373 | + using Image<Rgba32> image = Image.Load<Rgba32>(strict, data); |
| 374 | + }); |
| 375 | + |
| 376 | + Assert.Throws<InvalidImageContentException>(() => |
| 377 | + { |
| 378 | + using Image<Rgba32> image = Image.Load<Rgba32>(ancillary, data); |
| 379 | + }); |
| 380 | + } |
| 381 | + |
| 382 | + /// <summary> |
| 383 | + /// Accepts a standalone TEM marker between header segments under every integrity policy. |
| 384 | + /// </summary> |
| 385 | + [Fact] |
| 386 | + public void Decode_TemMarkerBetweenHeaderSegments_IsSkipped() |
| 387 | + { |
| 388 | + byte[] data = TestFile.Create(MarkerDirectory + "header-tem.jpg").Bytes; |
| 389 | + using Image<Rgba32> expected = Image.Load<Rgba32>(TestFile.Create(MarkerDirectory + "baseline.jpg").Bytes); |
| 390 | + |
| 391 | + // TEM has no length field. The following bytes belong to the next marker, not to TEM. |
| 392 | + foreach (SegmentIntegrityHandling integrity in Enum.GetValues<SegmentIntegrityHandling>()) |
| 393 | + { |
| 394 | + DecoderOptions options = new() { SegmentIntegrityHandling = integrity }; |
| 395 | + Assert.Equal(new Size(32, 24), Image.Identify(options, data).Size); |
| 396 | + |
| 397 | + using Image<Rgba32> actual = Image.Load<Rgba32>(options, data); |
| 398 | + ImageComparer.Exact.VerifySimilarity(expected, actual); |
| 399 | + } |
| 400 | + } |
| 401 | + |
| 402 | + /// <summary> |
| 403 | + /// Keeps required header lengths valid after a standalone TEM marker. |
| 404 | + /// </summary> |
| 405 | + [Fact] |
| 406 | + public void Decode_InvalidQuantizationSegmentAfterTem_RemainsFatal() |
| 407 | + { |
| 408 | + // TEM precedes DQT in this stored file. DQT declares more bytes than the file contains. |
| 409 | + // No image data has been read, so there is no safe point for recovery. |
| 410 | + byte[] data = TestFile.Create(MarkerDirectory + "header-tem-invalid-dqt-length.jpg").Bytes; |
| 411 | + |
| 412 | + foreach (SegmentIntegrityHandling integrity in Enum.GetValues<SegmentIntegrityHandling>()) |
| 413 | + { |
| 414 | + DecoderOptions options = new() { SegmentIntegrityHandling = integrity }; |
| 415 | + Assert.Throws<InvalidImageContentException>(() => |
| 416 | + { |
| 417 | + using Image<Rgba32> image = Image.Load<Rgba32>(options, data); |
| 418 | + }); |
| 419 | + } |
| 420 | + } |
| 421 | + |
303 | 422 | // https://github.com/SixLabors/ImageSharp/issues/2315 |
304 | 423 | // https://github.com/SixLabors/ImageSharp/issues/2334 |
305 | 424 | [Theory] |
|
0 commit comments