Browse Source

Merge branch 'master' into icc-color-conversion

pull/1567/head
James Jackson-South 5 years ago
committed by GitHub
parent
commit
eeb40f598f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 143
      .editorconfig
  2. 3
      .gitattributes
  3. 2
      .github/PULL_REQUEST_TEMPLATE.md
  4. 9
      Directory.Build.props
  5. 54
      ImageSharp.sln
  6. 2
      shared-infrastructure
  7. 565
      src/ImageSharp/Advanced/AotCompilerTools.cs
  8. 14
      src/ImageSharp/Advanced/PreserveAttribute.cs
  9. 32
      src/ImageSharp/Common/Helpers/RuntimeEnvironment.cs
  10. 59
      src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs
  11. 9
      src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs
  12. 21
      src/ImageSharp/Formats/Jpeg/JpegColorType.cs
  13. 1
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  14. 30
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  15. 241
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  16. 15
      src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
  17. 2
      src/ImageSharp/Formats/Jpeg/JpegSubsample.cs
  18. 6
      src/ImageSharp/Image.FromBytes.cs
  19. 10
      src/ImageSharp/ImageSharp.csproj
  20. 5
      src/ImageSharp/Processing/KnownDitherings.cs
  21. 1
      src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs
  22. 5
      src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs
  23. 57
      src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs
  24. 3
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs
  25. 210
      src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs
  26. 60
      src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs
  27. 104
      src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtils.cs
  28. 209
      src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs
  29. 4
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs
  30. 2
      tests/Directory.Build.targets
  31. 15
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  32. 28
      tests/ImageSharp.Benchmarks/Processing/Rotate.cs
  33. 28
      tests/ImageSharp.Benchmarks/Processing/Skew.cs
  34. 15
      tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj
  35. 6
      tests/ImageSharp.Tests.ProfilingSandbox/app.config
  36. 4
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  37. 31
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
  38. 4
      tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs
  39. 41
      tests/ImageSharp.Tests/Helpers/RuntimeEnvironmentTests.cs
  40. 15
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  41. 1
      tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs
  42. 2
      tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs
  43. 5
      tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs
  44. 2
      tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs
  45. 94
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs
  46. 2
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs
  47. 4
      tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
  48. 21
      tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs
  49. 61
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs
  50. 2
      tests/ImageSharp.Tests/TestUtilities/TestUtils.cs
  51. 3
      tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs
  52. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(-20,-10).png
  53. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50__original.png
  54. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png
  55. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png
  56. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png
  57. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png
  58. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png
  59. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png
  60. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png
  61. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png
  62. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png
  63. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png
  64. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png
  65. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png
  66. 4
      tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png
  67. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png
  68. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png
  69. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png
  70. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png
  71. 3
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png
  72. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png
  73. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png
  74. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png
  75. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png
  76. 3
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png
  77. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png
  78. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png
  79. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png
  80. 4
      tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png
  81. 4
      tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawTransformed.png
  82. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png
  83. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png
  84. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png
  85. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_OrderedDither.png
  86. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png
  87. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png
  88. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png
  89. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png
  90. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png
  91. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png
  92. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png
  93. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png
  94. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png
  95. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png
  96. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png
  97. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png
  98. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png
  99. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png
  100. 4
      tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png

143
.editorconfig

@ -1,5 +1,5 @@
# Version: 1.6.2 (Using https://semver.org/) # Version: 2.1.0 (Using https://semver.org/)
# Updated: 2020-11-02 # Updated: 2021-03-03
# See https://github.com/RehanSaeed/EditorConfig/releases for release notes. # See https://github.com/RehanSaeed/EditorConfig/releases for release notes.
# See https://github.com/RehanSaeed/EditorConfig for updates to this file. # See https://github.com/RehanSaeed/EditorConfig for updates to this file.
# See http://EditorConfig.org for more information about .editorconfig files. # See http://EditorConfig.org for more information about .editorconfig files.
@ -60,87 +60,84 @@ indent_size = 2
[*.{cmd,bat}] [*.{cmd,bat}]
end_of_line = crlf end_of_line = crlf
# Bash Files
[*.sh]
end_of_line = lf
# Makefiles # Makefiles
[Makefile] [Makefile]
indent_style = tab indent_style = tab
########################################## ##########################################
# File Header (Uncomment to support file headers) # Default .NET Code Style Severities
# https://docs.microsoft.com/visualstudio/ide/reference/add-file-header # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/configuration-options#scope
########################################## ##########################################
# [*.{cs,csx,cake,vb,vbx,tt,ttinclude}] [*.{cs,csx,cake,vb,vbx}]
file_header_template = Copyright (c) Six Labors.\nLicensed under the Apache License, Version 2.0. # Default Severity for all .NET Code Style rules below
dotnet_analyzer_diagnostic.severity = warning
# SA1636: File header copyright text should match
# Justification: .editorconfig supports file headers. If this is changed to a value other than "none", a stylecop.json file will need to added to the project.
# dotnet_diagnostic.SA1636.severity = none
########################################## ##########################################
# .NET Language Conventions # Language Rules
# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules
########################################## ##########################################
# .NET Code Style Settings # .NET Style Rules
# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#net-code-style-settings # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules#net-style-rules
[*.{cs,csx,cake,vb,vbx}] [*.{cs,csx,cake,vb,vbx}]
# "this." and "Me." qualifiers # "this." and "Me." qualifiers
# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#this-and-me
dotnet_style_qualification_for_field = true:warning dotnet_style_qualification_for_field = true:warning
dotnet_style_qualification_for_property = true:warning dotnet_style_qualification_for_property = true:warning
dotnet_style_qualification_for_method = true:warning dotnet_style_qualification_for_method = true:warning
dotnet_style_qualification_for_event = true:warning dotnet_style_qualification_for_event = true:warning
# Language keywords instead of framework type names for type references # Language keywords instead of framework type names for type references
# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#language-keywords
dotnet_style_predefined_type_for_locals_parameters_members = true:warning dotnet_style_predefined_type_for_locals_parameters_members = true:warning
dotnet_style_predefined_type_for_member_access = true:warning dotnet_style_predefined_type_for_member_access = true:warning
# Modifier preferences # Modifier preferences
# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#normalize-modifiers
dotnet_style_require_accessibility_modifiers = always:warning dotnet_style_require_accessibility_modifiers = always:warning
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning
visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:warning visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:warning
dotnet_style_readonly_field = true:warning dotnet_style_readonly_field = true:warning
# Parentheses preferences # Parentheses preferences
# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#parentheses-preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion dotnet_style_parentheses_in_other_operators = always_for_clarity:suggestion
# Expression-level preferences # Expression-level preferences
# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-level-preferences
dotnet_style_object_initializer = true:warning dotnet_style_object_initializer = true:warning
dotnet_style_collection_initializer = true:warning dotnet_style_collection_initializer = true:warning
dotnet_style_explicit_tuple_names = true:warning dotnet_style_explicit_tuple_names = true:warning
dotnet_style_prefer_inferred_tuple_names = true:warning dotnet_style_prefer_inferred_tuple_names = true:warning
dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
dotnet_style_prefer_auto_properties = true:warning dotnet_style_prefer_auto_properties = true:warning
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
dotnet_style_prefer_conditional_expression_over_assignment = false:suggestion dotnet_style_prefer_conditional_expression_over_assignment = false:suggestion
dotnet_diagnostic.IDE0045.severity = suggestion
dotnet_style_prefer_conditional_expression_over_return = false:suggestion dotnet_style_prefer_conditional_expression_over_return = false:suggestion
dotnet_diagnostic.IDE0046.severity = suggestion
dotnet_style_prefer_compound_assignment = true:warning dotnet_style_prefer_compound_assignment = true:warning
dotnet_style_prefer_simplified_interpolation = true:warning
dotnet_style_prefer_simplified_boolean_expressions = true:warning
# Null-checking preferences # Null-checking preferences
# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#null-checking-preferences
dotnet_style_coalesce_expression = true:warning dotnet_style_coalesce_expression = true:warning
dotnet_style_null_propagation = true:warning dotnet_style_null_propagation = true:warning
# Parameter preferences dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#parameter-preferences # File header preferences
dotnet_code_quality_unused_parameters = all:warning file_header_template = Copyright (c) Six Labors.\nLicensed under the Apache License, Version 2.0.
# More style options (Undocumented) # SA1636: File header copyright text should match
# https://github.com/MicrosoftDocs/visualstudio-docs/issues/3641 # Justification: .editorconfig supports file headers. If this is changed to a value other than "none", a stylecop.json file will need to added to the project.
# dotnet_diagnostic.SA1636.severity = none
# Undocumented
dotnet_style_operator_placement_when_wrapping = end_of_line dotnet_style_operator_placement_when_wrapping = end_of_line
# https://github.com/dotnet/roslyn/pull/40070
dotnet_style_prefer_simplified_interpolation = true:warning
# C# Code Style Settings # C# Style Rules
# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#c-code-style-settings # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules#c-style-rules
[*.{cs,csx,cake}] [*.{cs,csx,cake}]
# Implicit and explicit types # 'var' preferences
# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#implicit-and-explicit-types
csharp_style_var_for_built_in_types = never csharp_style_var_for_built_in_types = never
csharp_style_var_when_type_is_apparent = true:warning csharp_style_var_when_type_is_apparent = true:warning
csharp_style_var_elsewhere = false:warning csharp_style_var_elsewhere = false:warning
# Expression-bodied members # Expression-bodied members
# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-bodied-members
csharp_style_expression_bodied_methods = true:warning csharp_style_expression_bodied_methods = true:warning
csharp_style_expression_bodied_constructors = true:warning csharp_style_expression_bodied_constructors = true:warning
csharp_style_expression_bodied_operators = true:warning csharp_style_expression_bodied_operators = true:warning
@ -149,47 +146,64 @@ csharp_style_expression_bodied_indexers = true:warning
csharp_style_expression_bodied_accessors = true:warning csharp_style_expression_bodied_accessors = true:warning
csharp_style_expression_bodied_lambdas = true:warning csharp_style_expression_bodied_lambdas = true:warning
csharp_style_expression_bodied_local_functions = true:warning csharp_style_expression_bodied_local_functions = true:warning
# Pattern matching # Pattern matching preferences
# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#pattern-matching
csharp_style_pattern_matching_over_is_with_cast_check = true:warning csharp_style_pattern_matching_over_is_with_cast_check = true:warning
csharp_style_pattern_matching_over_as_with_null_check = true:warning csharp_style_pattern_matching_over_as_with_null_check = true:warning
# Inlined variable declarations csharp_style_prefer_switch_expression = true:warning
# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#inlined-variable-declarations csharp_style_prefer_pattern_matching = true:warning
csharp_style_inlined_variable_declaration = true:warning csharp_style_prefer_not_pattern = true:warning
# Expression-level preferences # Expression-level preferences
# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-level-preferences csharp_style_inlined_variable_declaration = true:warning
csharp_prefer_simple_default_expression = true:warning csharp_prefer_simple_default_expression = true:warning
csharp_style_pattern_local_over_anonymous_function = true:warning
csharp_style_deconstructed_variable_declaration = true:warning
csharp_style_prefer_index_operator = true:warning
csharp_style_prefer_range_operator = true:warning
csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
# "Null" checking preferences # "Null" checking preferences
# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#c-null-checking-preferences
csharp_style_throw_expression = true:warning csharp_style_throw_expression = true:warning
csharp_style_conditional_delegate_call = true:warning csharp_style_conditional_delegate_call = true:warning
# Code block preferences # Code block preferences
# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#code-block-preferences
csharp_prefer_braces = true:warning csharp_prefer_braces = true:warning
# Unused value preferences csharp_prefer_simple_using_statement = true:suggestion
# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#unused-value-preferences dotnet_diagnostic.IDE0063.severity = suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion # 'using' directive preferences
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
# Index and range preferences
# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#index-and-range-preferences
csharp_style_prefer_index_operator = true:warning
csharp_style_prefer_range_operator = true:warning
# Miscellaneous preferences
# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#miscellaneous-preferences
csharp_style_deconstructed_variable_declaration = true:warning
csharp_style_pattern_local_over_anonymous_function = true:warning
csharp_using_directive_placement = outside_namespace:warning csharp_using_directive_placement = outside_namespace:warning
# Modifier preferences
csharp_prefer_static_local_function = true:warning csharp_prefer_static_local_function = true:warning
csharp_prefer_simple_using_statement = true:suggestion
########################################## ##########################################
# .NET Formatting Conventions # Unnecessary Code Rules
# https://docs.microsoft.com/visualstudio/ide/editorconfig-code-style-settings-reference#formatting-conventions # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/unnecessary-code-rules
########################################## ##########################################
# Organize usings # .NET Unnecessary code rules
# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#organize-using-directives [*.{cs,csx,cake,vb,vbx}]
dotnet_code_quality_unused_parameters = all:warning
dotnet_remove_unnecessary_suppression_exclusions = none:warning
# C# Unnecessary code rules
[*.{cs,csx,cake}]
csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion
dotnet_diagnostic.IDE0058.severity = suggestion
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
dotnet_diagnostic.IDE0059.severity = suggestion
##########################################
# Formatting Rules
# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules
##########################################
# .NET formatting rules
# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#net-formatting-rules
[*.{cs,csx,cake,vb,vbx}]
# Organize using directives
dotnet_sort_system_directives_first = true dotnet_sort_system_directives_first = true
dotnet_separate_import_directive_groups = false
# C# formatting rules
# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/formatting-rules#c-formatting-rules
[*.{cs,csx,cake}]
# Newline options # Newline options
# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#new-line-options # https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#new-line-options
csharp_new_line_before_open_brace = all csharp_new_line_before_open_brace = all
@ -231,14 +245,14 @@ csharp_space_around_declaration_statements = false
csharp_space_before_open_square_brackets = false csharp_space_before_open_square_brackets = false
csharp_space_between_empty_square_brackets = false csharp_space_between_empty_square_brackets = false
csharp_space_between_square_brackets = false csharp_space_between_square_brackets = false
# Wrapping options # Wrap options
# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#wrap-options # https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#wrap-options
csharp_preserve_single_line_statements = false csharp_preserve_single_line_statements = false
csharp_preserve_single_line_blocks = true csharp_preserve_single_line_blocks = true
########################################## ##########################################
# .NET Naming Conventions # .NET Naming Rules
# https://docs.microsoft.com/visualstudio/ide/editorconfig-naming-conventions # https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/naming-rules
########################################## ##########################################
[*.{cs,csx,cake,vb,vbx}] [*.{cs,csx,cake,vb,vbx}]
@ -261,8 +275,9 @@ dotnet_naming_style.prefix_type_parameters_with_t_style.capitalization = pascal_
dotnet_naming_style.prefix_type_parameters_with_t_style.required_prefix = T dotnet_naming_style.prefix_type_parameters_with_t_style.required_prefix = T
# disallowed_style - Anything that has this style applied is marked as disallowed # disallowed_style - Anything that has this style applied is marked as disallowed
dotnet_naming_style.disallowed_style.capitalization = pascal_case dotnet_naming_style.disallowed_style.capitalization = pascal_case
dotnet_naming_style.disallowed_style.required_prefix = ____RULE_VIOLATION____ # Disabled while we investigate compatibility with VS 16.10
dotnet_naming_style.disallowed_style.required_suffix = ____RULE_VIOLATION____ #dotnet_naming_style.disallowed_style.required_prefix = ____RULE_VIOLATION____
#dotnet_naming_style.disallowed_style.required_suffix = ____RULE_VIOLATION____
# internal_error_style - This style should never occur... if it does, it indicates a bug in file or in the parser using the file # internal_error_style - This style should never occur... if it does, it indicates a bug in file or in the parser using the file
dotnet_naming_style.internal_error_style.capitalization = pascal_case dotnet_naming_style.internal_error_style.capitalization = pascal_case
dotnet_naming_style.internal_error_style.required_prefix = ____INTERNAL_ERROR____ dotnet_naming_style.internal_error_style.required_prefix = ____INTERNAL_ERROR____

3
.gitattributes

@ -86,7 +86,6 @@
*.dll binary *.dll binary
*.eot binary *.eot binary
*.exe binary *.exe binary
*.ktx binary
*.otf binary *.otf binary
*.pbm binary *.pbm binary
*.pdf binary *.pdf binary
@ -125,3 +124,5 @@
*.tga filter=lfs diff=lfs merge=lfs -text *.tga filter=lfs diff=lfs merge=lfs -text
*.webp filter=lfs diff=lfs merge=lfs -text *.webp filter=lfs diff=lfs merge=lfs -text
*.dds filter=lfs diff=lfs merge=lfs -text *.dds filter=lfs diff=lfs merge=lfs -text
*.ktx filter=lfs diff=lfs merge=lfs -text
*.ktx2 filter=lfs diff=lfs merge=lfs -text

2
.github/PULL_REQUEST_TEMPLATE.md

@ -2,7 +2,7 @@
- [ ] I have written a descriptive pull-request title - [ ] I have written a descriptive pull-request title
- [ ] I have verified that there are no overlapping [pull-requests](https://github.com/SixLabors/ImageSharp/pulls) open - [ ] I have verified that there are no overlapping [pull-requests](https://github.com/SixLabors/ImageSharp/pulls) open
- [ ] I have verified that I am following matches the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules :cop:. - [ ] I have verified that I am following the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules :cop:.
- [ ] I have provided test coverage for my change (where applicable) - [ ] I have provided test coverage for my change (where applicable)
### Description ### Description

9
Directory.Build.props

@ -18,4 +18,13 @@
<!-- Import the shared global .props file --> <!-- Import the shared global .props file -->
<Import Project="$(MSBuildThisFileDirectory)shared-infrastructure\msbuild\props\SixLabors.Global.props" /> <Import Project="$(MSBuildThisFileDirectory)shared-infrastructure\msbuild\props\SixLabors.Global.props" />
<!-- Custom Project Configuration doesn't seem to copy this setting properly. -->
<PropertyGroup Condition="$(Configuration.StartsWith('Debug')) == true">
<Optimize>false</Optimize>
</PropertyGroup>
<PropertyGroup Condition="$(Configuration.StartsWith('Release')) == true">
<Optimize>true</Optimize>
</PropertyGroup>
</Project> </Project>

54
ImageSharp.sln

@ -482,9 +482,15 @@ Global
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64 Debug|x64 = Debug|x64
Debug|x86 = Debug|x86 Debug|x86 = Debug|x86
Debug-InnerLoop|Any CPU = Debug-InnerLoop|Any CPU
Debug-InnerLoop|x64 = Debug-InnerLoop|x64
Debug-InnerLoop|x86 = Debug-InnerLoop|x86
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64 Release|x64 = Release|x64
Release|x86 = Release|x86 Release|x86 = Release|x86
Release-InnerLoop|Any CPU = Release-InnerLoop|Any CPU
Release-InnerLoop|x64 = Release-InnerLoop|x64
Release-InnerLoop|x86 = Release-InnerLoop|x86
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@ -493,48 +499,96 @@ Global
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x64.Build.0 = Debug|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x64.Build.0 = Debug|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x86.ActiveCfg = Debug|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x86.ActiveCfg = Debug|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x86.Build.0 = Debug|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x86.Build.0 = Debug|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|x64.ActiveCfg = Debug-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|x64.Build.0 = Debug-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|x86.ActiveCfg = Debug-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|x86.Build.0 = Debug-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|Any CPU.ActiveCfg = Release|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|Any CPU.Build.0 = Release|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|Any CPU.Build.0 = Release|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x64.ActiveCfg = Release|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x64.ActiveCfg = Release|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x64.Build.0 = Release|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x64.Build.0 = Release|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x86.ActiveCfg = Release|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x86.ActiveCfg = Release|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x86.Build.0 = Release|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x86.Build.0 = Release|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|Any CPU.ActiveCfg = Release-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|Any CPU.Build.0 = Release-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|x64.ActiveCfg = Release-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|x64.Build.0 = Release-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|x86.ActiveCfg = Release-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|x86.Build.0 = Release-InnerLoop|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|Any CPU.Build.0 = Debug|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x64.ActiveCfg = Debug|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x64.ActiveCfg = Debug|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x64.Build.0 = Debug|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x64.Build.0 = Debug|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x86.ActiveCfg = Debug|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x86.ActiveCfg = Debug|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x86.Build.0 = Debug|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x86.Build.0 = Debug|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|x64.ActiveCfg = Debug-InnerLoop|x64
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|x64.Build.0 = Debug-InnerLoop|x64
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|x86.ActiveCfg = Debug-InnerLoop|x86
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|x86.Build.0 = Debug-InnerLoop|x86
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|Any CPU.ActiveCfg = Release|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|Any CPU.Build.0 = Release|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|Any CPU.Build.0 = Release|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x64.ActiveCfg = Release|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x64.ActiveCfg = Release|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x64.Build.0 = Release|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x64.Build.0 = Release|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x86.ActiveCfg = Release|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x86.ActiveCfg = Release|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x86.Build.0 = Release|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x86.Build.0 = Release|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|Any CPU.ActiveCfg = Release-InnerLoop|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|Any CPU.Build.0 = Release-InnerLoop|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|x64.ActiveCfg = Release-InnerLoop|x64
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|x64.Build.0 = Release-InnerLoop|x64
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|x86.ActiveCfg = Release-InnerLoop|x86
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|x86.Build.0 = Release-InnerLoop|x86
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|Any CPU.Build.0 = Debug|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x64.ActiveCfg = Debug|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x64.ActiveCfg = Debug|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x64.Build.0 = Debug|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x64.Build.0 = Debug|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x86.ActiveCfg = Debug|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x86.ActiveCfg = Debug|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x86.Build.0 = Debug|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x86.Build.0 = Debug|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|x64.ActiveCfg = Debug-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|x64.Build.0 = Debug-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|x86.ActiveCfg = Debug-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|x86.Build.0 = Debug-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|Any CPU.ActiveCfg = Release|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|Any CPU.Build.0 = Release|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|Any CPU.Build.0 = Release|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x64.ActiveCfg = Release|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x64.ActiveCfg = Release|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x64.Build.0 = Release|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x64.Build.0 = Release|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x86.ActiveCfg = Release|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x86.ActiveCfg = Release|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x86.Build.0 = Release|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x86.Build.0 = Release|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|Any CPU.ActiveCfg = Release-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|Any CPU.Build.0 = Release-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|x64.ActiveCfg = Release-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|x64.Build.0 = Release-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|x86.ActiveCfg = Release-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|x86.Build.0 = Release-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|Any CPU.Build.0 = Debug|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x64.ActiveCfg = Debug|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x64.ActiveCfg = Debug|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x64.Build.0 = Debug|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x64.Build.0 = Debug|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x86.ActiveCfg = Debug|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x86.ActiveCfg = Debug|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x86.Build.0 = Debug|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x86.Build.0 = Debug|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|x64.ActiveCfg = Debug-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|x64.Build.0 = Debug-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|x86.ActiveCfg = Debug-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|x86.Build.0 = Debug-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|Any CPU.ActiveCfg = Release|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|Any CPU.Build.0 = Release|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Release|Any CPU.Build.0 = Release|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|x64.ActiveCfg = Release|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Release|x64.ActiveCfg = Release|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|x64.Build.0 = Release|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Release|x64.Build.0 = Release|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|x86.ActiveCfg = Release|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Release|x86.ActiveCfg = Release|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|x86.Build.0 = Release|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Release|x86.Build.0 = Release|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|Any CPU.ActiveCfg = Release-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|Any CPU.Build.0 = Release-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|x64.ActiveCfg = Release-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|x64.Build.0 = Release-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|x86.ActiveCfg = Release-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|x86.Build.0 = Release-InnerLoop|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

2
shared-infrastructure

@ -1 +1 @@
Subproject commit 06a733983486638b9e38197c7c6eb197ecac43e6 Subproject commit 41fff7bf7ddb1d118898db1ddba43b95ba6ed0bb

565
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -6,11 +6,26 @@ using System.Diagnostics.CodeAnalysis;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.ImageSharp.Processing.Processors.Binarization;
using SixLabors.ImageSharp.Processing.Processors.Convolution;
using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Dithering;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Processing.Processors.Effects;
using SixLabors.ImageSharp.Processing.Processors.Filters;
using SixLabors.ImageSharp.Processing.Processors.Normalization;
using SixLabors.ImageSharp.Processing.Processors.Overlays;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
namespace SixLabors.ImageSharp.Advanced namespace SixLabors.ImageSharp.Advanced
{ {
@ -25,180 +40,510 @@ namespace SixLabors.ImageSharp.Advanced
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
internal static class AotCompilerTools internal static class AotCompilerTools
{ {
static AotCompilerTools()
{
System.Runtime.CompilerServices.Unsafe.SizeOf<long>();
System.Runtime.CompilerServices.Unsafe.SizeOf<short>();
System.Runtime.CompilerServices.Unsafe.SizeOf<float>();
System.Runtime.CompilerServices.Unsafe.SizeOf<double>();
System.Runtime.CompilerServices.Unsafe.SizeOf<byte>();
System.Runtime.CompilerServices.Unsafe.SizeOf<int>();
System.Runtime.CompilerServices.Unsafe.SizeOf<Block8x8>();
System.Runtime.CompilerServices.Unsafe.SizeOf<Vector4>();
}
/// <summary> /// <summary>
/// This is the method that seeds the AoT compiler. /// This is the method that seeds the AoT compiler.
/// None of these seed methods needs to actually be called to seed the compiler. /// None of these seed methods needs to actually be called to seed the compiler.
/// The calls just need to be present when the code is compiled, and each implementation will be built. /// The calls just need to be present when the code is compiled, and each implementation will be built.
/// </summary> /// </summary>
/// <remarks>
/// This method doesn't actually do anything but serves an important purpose...
/// If you are running ImageSharp on iOS and try to call SaveAsGif, it will throw an exception:
/// "Attempting to JIT compile method... OctreeFrameQuantizer.ConstructPalette... while running in aot-only mode."
/// The reason this happens is the SaveAsGif method makes heavy use of generics, which are too confusing for the AoT
/// compiler used on Xamarin.iOS. It spins up the JIT compiler to try and figure it out, but that is an illegal op on
/// iOS so it bombs out.
/// If you are getting the above error, you need to call this method, which will pre-seed the AoT compiler with the
/// necessary methods to complete the SaveAsGif call. That's it, otherwise you should NEVER need this method!!!
/// </remarks>
[Preserve]
private static void SeedEverything() private static void SeedEverything()
{ {
Seed<A8>(); try
Seed<Argb32>(); {
Seed<Bgr24>(); Unsafe.SizeOf<long>();
Seed<Bgr565>(); Unsafe.SizeOf<short>();
Seed<Bgra32>(); Unsafe.SizeOf<float>();
Seed<Bgra4444>(); Unsafe.SizeOf<double>();
Seed<Bgra5551>(); Unsafe.SizeOf<byte>();
Seed<Byte4>(); Unsafe.SizeOf<int>();
Seed<L16>(); Unsafe.SizeOf<bool>();
Seed<L8>(); Unsafe.SizeOf<Block8x8>();
Seed<La16>(); Unsafe.SizeOf<Vector4>();
Seed<La32>();
Seed<HalfSingle>(); Seed<A8>();
Seed<HalfVector2>(); Seed<Argb32>();
Seed<HalfVector4>(); Seed<Bgr24>();
Seed<NormalizedByte2>(); Seed<Bgr565>();
Seed<NormalizedByte4>(); Seed<Bgra32>();
Seed<NormalizedShort2>(); Seed<Bgra4444>();
Seed<NormalizedShort4>(); Seed<Bgra5551>();
Seed<Rg32>(); Seed<Byte4>();
Seed<Rgb24>(); Seed<L16>();
Seed<Rgb48>(); Seed<L8>();
Seed<Rgba1010102>(); Seed<La16>();
Seed<Rgba32>(); Seed<La32>();
Seed<Rgba64>(); Seed<HalfSingle>();
Seed<RgbaVector>(); Seed<HalfVector2>();
Seed<Short2>(); Seed<HalfVector4>();
Seed<Short4>(); Seed<NormalizedByte2>();
Seed<NormalizedByte4>();
Seed<NormalizedShort2>();
Seed<NormalizedShort4>();
Seed<Rg32>();
Seed<Rgb24>();
Seed<Rgb48>();
Seed<Rgba1010102>();
Seed<Rgba32>();
Seed<Rgba64>();
Seed<RgbaVector>();
Seed<Short2>();
Seed<Short4>();
}
catch
{
// nop
}
throw new InvalidOperationException("This method is used for AOT code generation only. Do not call it at runtime.");
} }
/// <summary> /// <summary>
/// Seeds the compiler using the given pixel format. /// Seeds the compiler using the given pixel format.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
[Preserve]
private static void Seed<TPixel>() private static void Seed<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// This is we actually call all the individual methods you need to seed. // This is we actually call all the individual methods you need to seed.
AotCompileOctreeQuantizer<TPixel>(); AotCompileImage<TPixel>();
AotCompileWuQuantizer<TPixel>(); AotCompileImageProcessingContextFactory<TPixel>();
AotCompilePaletteQuantizer<TPixel>(); AotCompileImageEncoderInternals<TPixel>();
AotCompileDithering<TPixel>(); AotCompileImageDecoderInternals<TPixel>();
AotCompilePixelOperations<TPixel>(); AotCompileImageEncoders<TPixel>();
AotCompileImageDecoders<TPixel>();
AotCompileImageProcessors<TPixel>();
AotCompileGenericImageProcessors<TPixel>();
AotCompileResamplers<TPixel>();
AotCompileQuantizers<TPixel>();
AotCompilePixelSamplingStrategys<TPixel>();
AotCompileDithers<TPixel>();
AotCompileMemoryManagers<TPixel>();
Unsafe.SizeOf<TPixel>(); Unsafe.SizeOf<TPixel>();
AotCodec<TPixel>(new Formats.Png.PngDecoder(), new Formats.Png.PngEncoder());
AotCodec<TPixel>(new Formats.Bmp.BmpDecoder(), new Formats.Bmp.BmpEncoder());
AotCodec<TPixel>(new Formats.Gif.GifDecoder(), new Formats.Gif.GifEncoder());
AotCodec<TPixel>(new Formats.Jpeg.JpegDecoder(), new Formats.Jpeg.JpegEncoder());
// TODO: Do the discovery work to figure out what works and what doesn't. // TODO: Do the discovery work to figure out what works and what doesn't.
} }
/// <summary> /// <summary>
/// This method doesn't actually do anything but serves an important purpose... /// This method pre-seeds the <see cref="Image{TPixel}"/> for a given pixel format in the AoT compiler.
/// If you are running ImageSharp on iOS and try to call SaveAsGif, it will throw an exception:
/// "Attempting to JIT compile method... OctreeFrameQuantizer.ConstructPalette... while running in aot-only mode."
/// The reason this happens is the SaveAsGif method makes heavy use of generics, which are too confusing for the AoT
/// compiler used on Xamarin.iOS. It spins up the JIT compiler to try and figure it out, but that is an illegal op on
/// iOS so it bombs out.
/// If you are getting the above error, you need to call this method, which will pre-seed the AoT compiler with the
/// necessary methods to complete the SaveAsGif call. That's it, otherwise you should NEVER need this method!!!
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
private static void AotCompileOctreeQuantizer<TPixel>() [Preserve]
private static unsafe void AotCompileImage<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (var test = new OctreeQuantizer<TPixel>(Configuration.Default, new OctreeQuantizer().Options)) Image<TPixel> img = default;
{ img.CloneAs<A8>(default);
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1); img.CloneAs<Argb32>(default);
test.QuantizeFrame(frame, frame.Bounds()); img.CloneAs<Bgr24>(default);
} img.CloneAs<Bgr565>(default);
img.CloneAs<Bgra32>(default);
img.CloneAs<Bgra4444>(default);
img.CloneAs<Bgra5551>(default);
img.CloneAs<Byte4>(default);
img.CloneAs<L16>(default);
img.CloneAs<L8>(default);
img.CloneAs<La16>(default);
img.CloneAs<La32>(default);
img.CloneAs<HalfSingle>(default);
img.CloneAs<HalfVector2>(default);
img.CloneAs<HalfVector4>(default);
img.CloneAs<NormalizedByte2>(default);
img.CloneAs<NormalizedByte4>(default);
img.CloneAs<NormalizedShort2>(default);
img.CloneAs<NormalizedShort4>(default);
img.CloneAs<Rg32>(default);
img.CloneAs<Rgb24>(default);
img.CloneAs<Rgb48>(default);
img.CloneAs<Rgba1010102>(default);
img.CloneAs<Rgba32>(default);
img.CloneAs<Rgba64>(default);
img.CloneAs<RgbaVector>(default);
img.CloneAs<Short2>(default);
img.CloneAs<Short4>(default);
ImageFrame.LoadPixelData<TPixel>(default, default(ReadOnlySpan<TPixel>), default, default);
ImageFrame.LoadPixelData<TPixel>(default, default(ReadOnlySpan<byte>), default, default);
} }
/// <summary> /// <summary>
/// This method pre-seeds the WuQuantizer in the AoT compiler for iOS. /// This method pre-seeds the all <see cref="IImageProcessingContextFactory"/> in the AoT compiler.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
private static void AotCompileWuQuantizer<TPixel>() [Preserve]
private static void AotCompileImageProcessingContextFactory<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
=> default(DefaultImageOperationsProviderFactory).CreateImageProcessingContext<TPixel>(default, default, default);
/// <summary>
/// This method pre-seeds the all <see cref="IImageEncoderInternals"/> in the AoT compiler.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
[Preserve]
private static void AotCompileImageEncoderInternals<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (var test = new WuQuantizer<TPixel>(Configuration.Default, new WuQuantizer().Options)) default(BmpEncoderCore).Encode<TPixel>(default, default, default);
{ default(GifEncoderCore).Encode<TPixel>(default, default, default);
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1); default(JpegEncoderCore).Encode<TPixel>(default, default, default);
test.QuantizeFrame(frame, frame.Bounds()); default(PngEncoderCore).Encode<TPixel>(default, default, default);
} default(TgaEncoderCore).Encode<TPixel>(default, default, default);
} }
/// <summary> /// <summary>
/// This method pre-seeds the PaletteQuantizer in the AoT compiler for iOS. /// This method pre-seeds the all <see cref="IImageDecoderInternals"/> in the AoT compiler.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
private static void AotCompilePaletteQuantizer<TPixel>() [Preserve]
private static void AotCompileImageDecoderInternals<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (var test = (PaletteQuantizer<TPixel>)new PaletteQuantizer(Array.Empty<Color>()).CreatePixelSpecificQuantizer<TPixel>(Configuration.Default)) default(BmpDecoderCore).Decode<TPixel>(default, default, default);
{ default(GifDecoderCore).Decode<TPixel>(default, default, default);
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1); default(JpegDecoderCore).Decode<TPixel>(default, default, default);
test.QuantizeFrame(frame, frame.Bounds()); default(PngDecoderCore).Decode<TPixel>(default, default, default);
} default(TgaDecoderCore).Decode<TPixel>(default, default, default);
}
/// <summary>
/// This method pre-seeds the all <see cref="IImageEncoder"/> in the AoT compiler.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
[Preserve]
private static void AotCompileImageEncoders<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
AotCompileImageEncoder<TPixel, BmpEncoder>();
AotCompileImageEncoder<TPixel, GifEncoder>();
AotCompileImageEncoder<TPixel, JpegEncoder>();
AotCompileImageEncoder<TPixel, PngEncoder>();
AotCompileImageEncoder<TPixel, TgaEncoder>();
} }
/// <summary> /// <summary>
/// This method pre-seeds the default dithering engine (FloydSteinbergDiffuser) in the AoT compiler for iOS. /// This method pre-seeds the all <see cref="IImageDecoder"/> in the AoT compiler.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
private static void AotCompileDithering<TPixel>() [Preserve]
private static void AotCompileImageDecoders<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
ErrorDither errorDither = ErrorDither.FloydSteinberg; AotCompileImageDecoder<TPixel, BmpDecoder>();
OrderedDither orderedDither = OrderedDither.Bayer2x2; AotCompileImageDecoder<TPixel, GifDecoder>();
TPixel pixel = default; AotCompileImageDecoder<TPixel, JpegDecoder>();
using (var image = new ImageFrame<TPixel>(Configuration.Default, 1, 1)) AotCompileImageDecoder<TPixel, PngDecoder>();
{ AotCompileImageDecoder<TPixel, TgaDecoder>();
errorDither.Dither(image, image.Bounds(), pixel, pixel, 0, 0, 0);
orderedDither.Dither(pixel, 0, 0, 0, 0);
}
} }
/// <summary> /// <summary>
/// This method pre-seeds the decoder and encoder for a given pixel format in the AoT compiler for iOS. /// This method pre-seeds the <see cref="IImageEncoder"/> in the AoT compiler.
/// </summary> /// </summary>
/// <param name="decoder">The image decoder to seed.</param>
/// <param name="encoder">The image encoder to seed.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
private static void AotCodec<TPixel>(IImageDecoder decoder, IImageEncoder encoder) /// <typeparam name="TEncoder">The encoder.</typeparam>
[Preserve]
private static void AotCompileImageEncoder<TPixel, TEncoder>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
where TEncoder : class, IImageEncoder
{ {
try default(TEncoder).Encode<TPixel>(default, default);
{ default(TEncoder).EncodeAsync<TPixel>(default, default, default);
decoder.Decode<TPixel>(Configuration.Default, null); }
}
catch
{
}
try /// <summary>
{ /// This method pre-seeds the <see cref="IImageDecoder"/> in the AoT compiler.
encoder.Encode<TPixel>(null, null); /// </summary>
} /// <typeparam name="TPixel">The pixel format.</typeparam>
catch /// <typeparam name="TDecoder">The decoder.</typeparam>
{ [Preserve]
} private static void AotCompileImageDecoder<TPixel, TDecoder>()
where TPixel : unmanaged, IPixel<TPixel>
where TDecoder : class, IImageDecoder
{
default(TDecoder).Decode<TPixel>(default, default);
default(TDecoder).DecodeAsync<TPixel>(default, default, default);
}
/// <summary>
/// This method pre-seeds the all <see cref="IImageProcessor" /> in the AoT compiler.
/// </summary>
/// <remarks>
/// There is no structure that implements ISwizzler.
/// </remarks>
/// <typeparam name="TPixel">The pixel format.</typeparam>
[Preserve]
private static void AotCompileImageProcessors<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
AotCompileImageProcessor<TPixel, CloningImageProcessor>();
AotCompileImageProcessor<TPixel, CropProcessor>();
AotCompileImageProcessor<TPixel, AffineTransformProcessor>();
AotCompileImageProcessor<TPixel, ProjectiveTransformProcessor>();
AotCompileImageProcessor<TPixel, RotateProcessor>();
AotCompileImageProcessor<TPixel, SkewProcessor>();
AotCompileImageProcessor<TPixel, ResizeProcessor>();
AotCompileImageProcessor<TPixel, EntropyCropProcessor>();
AotCompileImageProcessor<TPixel, AutoOrientProcessor>();
AotCompileImageProcessor<TPixel, FlipProcessor>();
AotCompileImageProcessor<TPixel, QuantizeProcessor>();
AotCompileImageProcessor<TPixel, BackgroundColorProcessor>();
AotCompileImageProcessor<TPixel, GlowProcessor>();
AotCompileImageProcessor<TPixel, VignetteProcessor>();
AotCompileImageProcessor<TPixel, AdaptiveHistogramEqualizationProcessor>();
AotCompileImageProcessor<TPixel, AdaptiveHistogramEqualizationSlidingWindowProcessor>();
AotCompileImageProcessor<TPixel, GlobalHistogramEqualizationProcessor>();
AotCompileImageProcessor<TPixel, AchromatomalyProcessor>();
AotCompileImageProcessor<TPixel, AchromatopsiaProcessor>();
AotCompileImageProcessor<TPixel, BlackWhiteProcessor>();
AotCompileImageProcessor<TPixel, BrightnessProcessor>();
AotCompileImageProcessor<TPixel, ContrastProcessor>();
AotCompileImageProcessor<TPixel, DeuteranomalyProcessor>();
AotCompileImageProcessor<TPixel, DeuteranopiaProcessor>();
AotCompileImageProcessor<TPixel, FilterProcessor>();
AotCompileImageProcessor<TPixel, GrayscaleBt601Processor>();
AotCompileImageProcessor<TPixel, GrayscaleBt709Processor>();
AotCompileImageProcessor<TPixel, HueProcessor>();
AotCompileImageProcessor<TPixel, InvertProcessor>();
AotCompileImageProcessor<TPixel, KodachromeProcessor>();
AotCompileImageProcessor<TPixel, LightnessProcessor>();
AotCompileImageProcessor<TPixel, LomographProcessor>();
AotCompileImageProcessor<TPixel, OpacityProcessor>();
AotCompileImageProcessor<TPixel, PolaroidProcessor>();
AotCompileImageProcessor<TPixel, ProtanomalyProcessor>();
AotCompileImageProcessor<TPixel, ProtanopiaProcessor>();
AotCompileImageProcessor<TPixel, SaturateProcessor>();
AotCompileImageProcessor<TPixel, SepiaProcessor>();
AotCompileImageProcessor<TPixel, TritanomalyProcessor>();
AotCompileImageProcessor<TPixel, TritanopiaProcessor>();
AotCompileImageProcessor<TPixel, OilPaintingProcessor>();
AotCompileImageProcessor<TPixel, PixelateProcessor>();
AotCompileImageProcessor<TPixel, PixelRowDelegateProcessor>();
AotCompileImageProcessor<TPixel, PositionAwarePixelRowDelegateProcessor>();
AotCompileImageProcessor<TPixel, DrawImageProcessor>();
AotCompileImageProcessor<TPixel, PaletteDitherProcessor>();
AotCompileImageProcessor<TPixel, BokehBlurProcessor>();
AotCompileImageProcessor<TPixel, BoxBlurProcessor>();
AotCompileImageProcessor<TPixel, EdgeDetector2DProcessor>();
AotCompileImageProcessor<TPixel, EdgeDetectorCompassProcessor>();
AotCompileImageProcessor<TPixel, EdgeDetectorProcessor>();
AotCompileImageProcessor<TPixel, GaussianBlurProcessor>();
AotCompileImageProcessor<TPixel, GaussianSharpenProcessor>();
AotCompileImageProcessor<TPixel, AdaptiveThresholdProcessor>();
AotCompileImageProcessor<TPixel, BinaryThresholdProcessor>();
AotCompilerCloningImageProcessor<TPixel, CloningImageProcessor>();
AotCompilerCloningImageProcessor<TPixel, CropProcessor>();
AotCompilerCloningImageProcessor<TPixel, AffineTransformProcessor>();
AotCompilerCloningImageProcessor<TPixel, ProjectiveTransformProcessor>();
AotCompilerCloningImageProcessor<TPixel, RotateProcessor>();
AotCompilerCloningImageProcessor<TPixel, SkewProcessor>();
AotCompilerCloningImageProcessor<TPixel, ResizeProcessor>();
}
/// <summary>
/// This method pre-seeds the <see cref="IImageProcessor" /> in the AoT compiler.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <typeparam name="TProc">The processor type</typeparam>
[Preserve]
private static void AotCompileImageProcessor<TPixel, TProc>()
where TPixel : unmanaged, IPixel<TPixel>
where TProc : class, IImageProcessor
=> default(TProc).CreatePixelSpecificProcessor<TPixel>(default, default, default);
/// <summary>
/// This method pre-seeds the <see cref="ICloningImageProcessor" /> in the AoT compiler.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <typeparam name="TProc">The processor type</typeparam>
[Preserve]
private static void AotCompilerCloningImageProcessor<TPixel, TProc>()
where TPixel : unmanaged, IPixel<TPixel>
where TProc : class, ICloningImageProcessor
=> default(TProc).CreatePixelSpecificCloningProcessor<TPixel>(default, default, default);
/// <summary>
/// This method pre-seeds the all <see cref="IImageProcessor"/> in the AoT compiler.
/// </summary>
/// <remarks>
/// There is no structure that implements ISwizzler.
/// </remarks>
/// <typeparam name="TPixel">The pixel format.</typeparam>
[Preserve]
private static void AotCompileGenericImageProcessors<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
AotCompileGenericCloningImageProcessor<TPixel, CropProcessor<TPixel>>();
AotCompileGenericCloningImageProcessor<TPixel, AffineTransformProcessor<TPixel>>();
AotCompileGenericCloningImageProcessor<TPixel, ProjectiveTransformProcessor<TPixel>>();
AotCompileGenericCloningImageProcessor<TPixel, ResizeProcessor<TPixel>>();
AotCompileGenericCloningImageProcessor<TPixel, RotateProcessor<TPixel>>();
}
/// <summary>
/// This method pre-seeds the <see cref="ICloningImageProcessor{TPixel}" /> in the AoT compiler.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <typeparam name="TProc">The processor type</typeparam>
[Preserve]
private static void AotCompileGenericCloningImageProcessor<TPixel, TProc>()
where TPixel : unmanaged, IPixel<TPixel>
where TProc : class, ICloningImageProcessor<TPixel>
=> default(TProc).CloneAndExecute();
/// <summary>
/// This method pre-seeds the all<see cref="IResampler" /> in the AoT compiler.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
[Preserve]
private static void AotCompileResamplers<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
AotCompileResampler<TPixel, BicubicResampler>();
AotCompileResampler<TPixel, BoxResampler>();
AotCompileResampler<TPixel, CubicResampler>();
AotCompileResampler<TPixel, LanczosResampler>();
AotCompileResampler<TPixel, NearestNeighborResampler>();
AotCompileResampler<TPixel, TriangleResampler>();
AotCompileResampler<TPixel, WelchResampler>();
}
/// <summary>
/// This method pre-seeds the <see cref="IResampler" /> in the AoT compiler.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <typeparam name="TResampler">The processor type</typeparam>
[Preserve]
private static void AotCompileResampler<TPixel, TResampler>()
where TPixel : unmanaged, IPixel<TPixel>
where TResampler : struct, IResampler
{
default(TResampler).ApplyTransform<TPixel>(default);
default(AffineTransformProcessor<TPixel>).ApplyTransform<TResampler>(default);
default(ProjectiveTransformProcessor<TPixel>).ApplyTransform<TResampler>(default);
default(ResizeProcessor<TPixel>).ApplyTransform<TResampler>(default);
default(RotateProcessor<TPixel>).ApplyTransform<TResampler>(default);
}
/// <summary>
/// This method pre-seeds the all <see cref="IQuantizer" /> in the AoT compiler.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
[Preserve]
private static void AotCompileQuantizers<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
AotCompileQuantizer<TPixel, OctreeQuantizer>();
AotCompileQuantizer<TPixel, PaletteQuantizer>();
AotCompileQuantizer<TPixel, WebSafePaletteQuantizer>();
AotCompileQuantizer<TPixel, WernerPaletteQuantizer>();
AotCompileQuantizer<TPixel, WuQuantizer>();
}
/// <summary>
/// This method pre-seeds the <see cref="IQuantizer" /> in the AoT compiler.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <typeparam name="TQuantizer">The quantizer type</typeparam>
[Preserve]
private static void AotCompileQuantizer<TPixel, TQuantizer>()
where TPixel : unmanaged, IPixel<TPixel>
where TQuantizer : class, IQuantizer
{
default(TQuantizer).CreatePixelSpecificQuantizer<TPixel>(default);
default(TQuantizer).CreatePixelSpecificQuantizer<TPixel>(default, default);
}
/// <summary>
/// This method pre-seeds the <see cref="IPixelSamplingStrategy" /> in the AoT compiler.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
[Preserve]
private static void AotCompilePixelSamplingStrategys<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
default(DefaultPixelSamplingStrategy).EnumeratePixelRegions<TPixel>(default);
default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions<TPixel>(default);
}
/// <summary>
/// This method pre-seeds the all <see cref="IDither" /> in the AoT compiler.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
[Preserve]
private static void AotCompileDithers<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
AotCompileDither<TPixel, ErrorDither>();
AotCompileDither<TPixel, OrderedDither>();
}
/// <summary>
/// This method pre-seeds the <see cref="IDither" /> in the AoT compiler.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <typeparam name="TDither">The dither.</typeparam>
[Preserve]
private static void AotCompileDither<TPixel, TDither>()
where TPixel : unmanaged, IPixel<TPixel>
where TDither : struct, IDither
{
var octree = default(OctreeQuantizer<TPixel>);
default(TDither).ApplyQuantizationDither<OctreeQuantizer<TPixel>, TPixel>(ref octree, default, default, default);
var palette = default(PaletteQuantizer<TPixel>);
default(TDither).ApplyQuantizationDither<PaletteQuantizer<TPixel>, TPixel>(ref palette, default, default, default);
var wu = default(WuQuantizer<TPixel>);
default(TDither).ApplyQuantizationDither<WuQuantizer<TPixel>, TPixel>(ref wu, default, default, default);
default(TDither).ApplyPaletteDither<PaletteDitherProcessor<TPixel>.DitherProcessor, TPixel>(default, default, default);
}
/// <summary>
/// This method pre-seeds the all <see cref="MemoryAllocator" /> in the AoT compiler.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
[Preserve]
private static void AotCompileMemoryManagers<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
AotCompileMemoryManager<TPixel, ArrayPoolMemoryAllocator>();
AotCompileMemoryManager<TPixel, SimpleGcMemoryAllocator>();
} }
/// <summary> /// <summary>
/// This method pre-seeds the PixelOperations engine for the AoT compiler on iOS. /// This method pre-seeds the <see cref="MemoryAllocator" /> in the AoT compiler.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
private static void AotCompilePixelOperations<TPixel>() /// <typeparam name="TBuffer">The buffer.</typeparam>
[Preserve]
private static void AotCompileMemoryManager<TPixel, TBuffer>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
where TBuffer : MemoryAllocator
{ {
var pixelOp = new PixelOperations<TPixel>(); default(TBuffer).Allocate<long>(default, default);
pixelOp.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.Clear); default(TBuffer).Allocate<short>(default, default);
default(TBuffer).Allocate<float>(default, default);
default(TBuffer).Allocate<double>(default, default);
default(TBuffer).Allocate<byte>(default, default);
default(TBuffer).Allocate<int>(default, default);
default(TBuffer).Allocate<bool>(default, default);
default(TBuffer).Allocate<decimal>(default, default);
default(TBuffer).Allocate<Block8x8>(default, default);
default(TBuffer).Allocate<Vector4>(default, default);
default(TBuffer).Allocate<TPixel>(default, default);
} }
} }
} }

14
src/ImageSharp/Advanced/PreserveAttribute.cs

@ -0,0 +1,14 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Advanced
{
/// <summary>
/// This is necessary to avoid being excluded from compilation in environments that do AOT builds, such as Unity's IL2CPP and Xamarin.
/// The only thing that matters is the class name.
/// There is no need to use or inherit from the PreserveAttribute class in each environment.
/// </summary>
internal sealed class PreserveAttribute : System.Attribute
{
}
}

32
src/ImageSharp/Common/Helpers/RuntimeEnvironment.cs

@ -0,0 +1,32 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Provides information about the .NET runtime installation.
/// Many methods defer to <see cref="RuntimeInformation"/> when available.
/// </summary>
internal static class RuntimeEnvironment
{
private static readonly Lazy<bool> IsNetCoreLazy = new Lazy<bool>(() => FrameworkDescription.StartsWith(".NET Core", StringComparison.OrdinalIgnoreCase));
/// <summary>
/// Gets a value indicating whether the .NET installation is .NET Core 3.1 or lower.
/// </summary>
public static bool IsNetCore => IsNetCoreLazy.Value;
/// <summary>
/// Gets the name of the .NET installation on which an app is running.
/// </summary>
public static string FrameworkDescription => RuntimeInformation.FrameworkDescription;
/// <summary>
/// Indicates whether the current application is running on the specified platform.
/// </summary>
public static bool IsOSPlatform(OSPlatform osPlatform) => RuntimeInformation.IsOSPlatform(osPlatform);
}
}

59
src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs

@ -0,0 +1,59 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// On-stack worker struct to efficiently encapsulate the TPixel -> L8 -> Y conversion chain of 8x8 pixel blocks.
/// </summary>
/// <typeparam name="TPixel">The pixel type to work on</typeparam>
internal ref struct LuminanceForwardConverter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// The Y component
/// </summary>
public Block8x8F Y;
/// <summary>
/// Temporal 8x8 block to hold TPixel data
/// </summary>
private GenericBlock8x8<TPixel> pixelBlock;
/// <summary>
/// Temporal RGB block
/// </summary>
private GenericBlock8x8<L8> l8Block;
public static LuminanceForwardConverter<TPixel> Create()
{
var result = default(LuminanceForwardConverter<TPixel>);
return result;
}
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (<see cref="Y"/>)
/// </summary>
public void Convert(ImageFrame<TPixel> frame, int x, int y, ref RowOctet<TPixel> currentRows)
{
this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, ref currentRows);
Span<L8> l8Span = this.l8Block.AsSpanUnsafe();
PixelOperations<TPixel>.Instance.ToL8(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), l8Span);
ref Block8x8F yBlock = ref this.Y;
ref L8 l8Start = ref l8Span[0];
for (int i = 0; i < 64; i++)
{
ref L8 c = ref Unsafe.Add(ref l8Start, i);
yBlock[i] = c.PackedValue;
}
}
}
}

9
src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg namespace SixLabors.ImageSharp.Formats.Jpeg
@ -20,5 +20,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
/// <value>The subsample ratio of the jpg image.</value> /// <value>The subsample ratio of the jpg image.</value>
JpegSubsample? Subsample { get; } JpegSubsample? Subsample { get; }
/// <summary>
/// Gets the color type.
/// </summary>
JpegColorType? ColorType { get; }
} }
} }

21
src/ImageSharp/Formats/Jpeg/JpegColorType.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg
{
/// <summary>
/// Provides enumeration of available JPEG color types.
/// </summary>
public enum JpegColorType : byte
{
/// <summary>
/// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification.
/// </summary>
YCbCr = 0,
/// <summary>
/// Single channel, luminance.
/// </summary>
Luminance = 1
}
}

1
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -912,6 +912,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.Frame.MaxHorizontalFactor = maxH; this.Frame.MaxHorizontalFactor = maxH;
this.Frame.MaxVerticalFactor = maxV; this.Frame.MaxVerticalFactor = maxV;
this.ColorSpace = this.DeduceJpegColorSpace(); this.ColorSpace = this.DeduceJpegColorSpace();
this.Metadata.GetJpegMetadata().ColorType = this.ColorSpace == JpegColorSpace.Grayscale ? JpegColorType.Luminance : JpegColorType.YCbCr;
this.Frame.InitComponents(); this.Frame.InitComponents();
this.ImageSizeInMCU = new Size(this.Frame.McusPerLine, this.Frame.McusPerColumn); this.ImageSizeInMCU = new Size(this.Frame.McusPerLine, this.Frame.McusPerColumn);
} }

30
src/ImageSharp/Formats/Jpeg/JpegEncoder.cs

@ -25,6 +25,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
public JpegSubsample? Subsample { get; set; } public JpegSubsample? Subsample { get; set; }
/// <summary>
/// Gets or sets the color type, that will be used to encode the image.
/// </summary>
public JpegColorType? ColorType { get; set; }
/// <summary> /// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>. /// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary> /// </summary>
@ -35,6 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var encoder = new JpegEncoderCore(this); var encoder = new JpegEncoderCore(this);
this.InitializeColorType<TPixel>(image);
encoder.Encode(image, stream); encoder.Encode(image, stream);
} }
@ -50,7 +56,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var encoder = new JpegEncoderCore(this); var encoder = new JpegEncoderCore(this);
this.InitializeColorType<TPixel>(image);
return encoder.EncodeAsync(image, stream, cancellationToken); return encoder.EncodeAsync(image, stream, cancellationToken);
} }
/// <summary>
/// If ColorType was not set, set it based on the given image.
/// </summary>
private void InitializeColorType<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
// First inspect the image metadata.
if (this.ColorType == null)
{
JpegMetadata metadata = image.Metadata.GetJpegMetadata();
this.ColorType = metadata.ColorType;
}
// Secondly, inspect the pixel type.
if (this.ColorType == null)
{
bool isGrayscale =
typeof(TPixel) == typeof(L8) || typeof(TPixel) == typeof(L16) ||
typeof(TPixel) == typeof(La16) || typeof(TPixel) == typeof(La32);
this.ColorType = isGrayscale ? JpegColorType.Luminance : JpegColorType.YCbCr;
}
}
} }
} }

241
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -57,6 +57,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
private readonly int? quality; private readonly int? quality;
/// <summary>
/// Gets or sets the subsampling method to use.
/// </summary>
private readonly JpegColorType? colorType;
/// <summary> /// <summary>
/// The accumulated bits to write to the stream. /// The accumulated bits to write to the stream.
/// </summary> /// </summary>
@ -90,6 +95,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
this.quality = options.Quality; this.quality = options.Quality;
this.subsample = options.Subsample; this.subsample = options.Subsample;
this.colorType = options.ColorType;
} }
/// <summary> /// <summary>
@ -115,42 +121,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
8, 8, 8, 8, 8, 8,
}; };
/// <summary>
/// Gets the SOS (Start Of Scan) 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&lt;&lt;4 | 0x00.
/// </summary>
// The C# compiler emits this as a compile-time constant embedded in the PE file.
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
// More details can be found: https://github.com/dotnet/roslyn/pull/24621
private static ReadOnlySpan<byte> SosHeaderYCbCr => new byte[]
{
JpegConstants.Markers.XFF, JpegConstants.Markers.SOS,
// Marker
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)
};
/// <summary> /// <summary>
/// Gets the unscaled quantization tables in zig-zag order. Each /// Gets the unscaled quantization tables in zig-zag order. Each
/// encoder copies and scales the tables according to its quality parameter. /// encoder copies and scales the tables according to its quality parameter.
@ -212,6 +182,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.outputStream = stream; this.outputStream = stream;
ImageMetadata metadata = image.Metadata; ImageMetadata metadata = image.Metadata;
// Compute number of components based on color type in options.
int componentCount = (this.colorType == JpegColorType.Luminance) ? 1 : 3;
// System.Drawing produces identical output for jpegs with a quality parameter of 0 and 1. // System.Drawing produces identical output for jpegs with a quality parameter of 0 and 1.
int qlty = Numerics.Clamp(this.quality ?? metadata.GetJpegMetadata().Quality, 1, 100); int qlty = Numerics.Clamp(this.quality ?? metadata.GetJpegMetadata().Quality, 1, 100);
this.subsample ??= qlty >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420; this.subsample ??= qlty >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420;
@ -229,10 +202,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Initialize the quantization tables. // Initialize the quantization tables.
InitQuantizationTable(0, scale, ref this.luminanceQuantTable); InitQuantizationTable(0, scale, ref this.luminanceQuantTable);
InitQuantizationTable(1, scale, ref this.chrominanceQuantTable); if (componentCount > 1)
{
// Compute number of components based on input image type. InitQuantizationTable(1, scale, ref this.chrominanceQuantTable);
const int componentCount = 3; }
// Write the Start Of Image marker. // Write the Start Of Image marker.
this.WriteApplicationHeader(metadata); this.WriteApplicationHeader(metadata);
@ -250,7 +223,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.WriteDefineHuffmanTables(componentCount); this.WriteDefineHuffmanTables(componentCount);
// Write the image data. // Write the image data.
this.WriteStartOfScan(image, cancellationToken); this.WriteStartOfScan(image, componentCount, cancellationToken);
// Write the End Of Image marker. // Write the End Of Image marker.
this.buffer[0] = JpegConstants.Markers.XFF; this.buffer[0] = JpegConstants.Markers.XFF;
@ -468,6 +441,55 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
} }
/// <summary>
/// Encodes the image with no chroma, just luminance.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
private void EncodeGrayscale<TPixel>(Image<TPixel> pixels, CancellationToken cancellationToken, ref byte emitBufferBase)
where TPixel : unmanaged, IPixel<TPixel>
{
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
// (Partially done with YCbCrForwardConverter<TPixel>)
Block8x8F temp1 = default;
Block8x8F temp2 = default;
Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable;
var unzig = ZigZag.CreateUnzigTable();
// ReSharper disable once InconsistentNaming
int prevDCY = 0;
var pixelConverter = LuminanceForwardConverter<TPixel>.Create();
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
for (int y = 0; y < pixels.Height; y += 8)
{
cancellationToken.ThrowIfCancellationRequested();
currentRows.Update(pixelBuffer, y);
for (int x = 0; x < pixels.Width; x += 8)
{
pixelConverter.Convert(frame, x, y, ref currentRows);
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
prevDCY,
ref pixelConverter.Y,
ref temp1,
ref temp2,
ref onStackLuminanceQuantTable,
ref unzig,
ref emitBufferBase);
}
}
}
/// <summary> /// <summary>
/// Writes the application header containing the JFIF identifier plus extra data. /// Writes the application header containing the JFIF identifier plus extra data.
/// </summary> /// </summary>
@ -896,24 +918,36 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
0x01 0x01
}; };
switch (this.subsample) if (this.colorType == JpegColorType.Luminance)
{ {
case JpegSubsample.Ratio444: subsamples = stackalloc byte[]
subsamples = stackalloc byte[] {
{ 0x11,
0x11, 0x00,
0x11, 0x00
0x11 };
}; }
break; else
case JpegSubsample.Ratio420: {
subsamples = stackalloc byte[] switch (this.subsample)
{ {
0x22, case JpegSubsample.Ratio444:
0x11, subsamples = stackalloc byte[]
0x11 {
}; 0x11,
break; 0x11,
0x11
};
break;
case JpegSubsample.Ratio420:
subsamples = stackalloc byte[]
{
0x22,
0x11,
0x11
};
break;
}
} }
// Length (high byte, low byte), 8 + components * 3. // Length (high byte, low byte), 8 + components * 3.
@ -926,26 +960,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.buffer[4] = (byte)(width & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported this.buffer[4] = (byte)(width & 0xff); // (2 bytes, Hi-Lo), must be > 0 if DNL not supported
this.buffer[5] = (byte)componentCount; this.buffer[5] = (byte)componentCount;
// Number of components (1 byte), usually 1 = Gray scaled, 3 = color YCbCr or YIQ, 4 = color CMYK) for (int i = 0; i < componentCount; i++)
if (componentCount == 1)
{ {
this.buffer[6] = 1; int i3 = 3 * i;
this.buffer[i3 + 6] = (byte)(i + 1);
// No subsampling for grayscale images. this.buffer[i3 + 7] = subsamples[i];
this.buffer[7] = 0x11; this.buffer[i3 + 8] = chroma[i];
this.buffer[8] = 0x00;
}
else
{
for (int i = 0; i < componentCount; i++)
{
int i3 = 3 * i;
this.buffer[i3 + 6] = (byte)(i + 1);
// We use 4:2:0 chroma subsampling by default.
this.buffer[i3 + 7] = subsamples[i];
this.buffer[i3 + 8] = chroma[i];
}
} }
this.outputStream.Write(this.buffer, 0, (3 * (componentCount - 1)) + 9); this.outputStream.Write(this.buffer, 0, (3 * (componentCount - 1)) + 9);
@ -956,22 +977,70 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The pixel accessor providing access to the image pixels.</param> /// <param name="image">The pixel accessor providing access to the image pixels.</param>
/// <param name="componentCount">The number of components in a pixel.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param> /// <param name="cancellationToken">The token to monitor for cancellation.</param>
private void WriteStartOfScan<TPixel>(Image<TPixel> image, CancellationToken cancellationToken) private void WriteStartOfScan<TPixel>(Image<TPixel> image, int componentCount, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) // TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
// TODO: We should allow grayscale writing. Span<byte> componentId = stackalloc byte[]
this.outputStream.Write(SosHeaderYCbCr); {
0x01,
0x02,
0x03
};
Span<byte> huffmanId = stackalloc byte[]
{
0x00,
0x11,
0x11
};
// Write the SOS (Start Of Scan) 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&lt;&lt;4 | 0x00.
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.SOS;
// Length (high byte, low byte), must be 6 + 2 * (number of components in scan)
int sosSize = 6 + (2 * componentCount);
this.buffer[2] = 0x00;
this.buffer[3] = (byte)sosSize;
this.buffer[4] = (byte)componentCount; // Number of components in a scan
for (int i = 0; i < componentCount; i++)
{
int i2 = 2 * i;
this.buffer[i2 + 5] = componentId[i]; // Component Id
this.buffer[i2 + 6] = huffmanId[i]; // DC/AC Huffman table
}
this.buffer[sosSize - 1] = 0x00; // Ss - Start of spectral selection.
this.buffer[sosSize] = 0x3f; // Se - End of spectral selection.
this.buffer[sosSize + 1] = 0x00; // Ah + Ah (Successive approximation bit position high + low)
this.outputStream.Write(this.buffer, 0, sosSize + 2);
ref byte emitBufferBase = ref MemoryMarshal.GetReference<byte>(this.emitBuffer); ref byte emitBufferBase = ref MemoryMarshal.GetReference<byte>(this.emitBuffer);
switch (this.subsample) if (this.colorType == JpegColorType.Luminance)
{ {
case JpegSubsample.Ratio444: this.EncodeGrayscale(image, cancellationToken, ref emitBufferBase);
this.Encode444(image, cancellationToken, ref emitBufferBase); }
break; else
case JpegSubsample.Ratio420: {
this.Encode420(image, cancellationToken, ref emitBufferBase); switch (this.subsample)
break; {
case JpegSubsample.Ratio444:
this.Encode444(image, cancellationToken, ref emitBufferBase);
break;
case JpegSubsample.Ratio420:
this.Encode420(image, cancellationToken, ref emitBufferBase);
break;
}
} }
// Pad the last byte with 1's. // Pad the last byte with 1's.

15
src/ImageSharp/Formats/Jpeg/JpegMetadata.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg namespace SixLabors.ImageSharp.Formats.Jpeg
@ -19,14 +19,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// Initializes a new instance of the <see cref="JpegMetadata"/> class. /// Initializes a new instance of the <see cref="JpegMetadata"/> class.
/// </summary> /// </summary>
/// <param name="other">The metadata to create an instance from.</param> /// <param name="other">The metadata to create an instance from.</param>
private JpegMetadata(JpegMetadata other) => this.Quality = other.Quality; private JpegMetadata(JpegMetadata other)
{
this.Quality = other.Quality;
this.ColorType = other.ColorType;
}
/// <summary> /// <summary>
/// Gets or sets the encoded quality. /// Gets or sets the encoded quality.
/// </summary> /// </summary>
public int Quality { get; set; } = 75; public int Quality { get; set; } = 75;
/// <summary>
/// Gets or sets the encoded quality.
/// </summary>
public JpegColorType? ColorType { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public IDeepCloneable DeepClone() => new JpegMetadata(this); public IDeepCloneable DeepClone() => new JpegMetadata(this);
} }
} }

2
src/ImageSharp/Formats/Jpeg/JpegSubsample.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg namespace SixLabors.ImageSharp.Formats.Jpeg

6
src/ImageSharp/Image.FromBytes.cs

@ -91,9 +91,9 @@ namespace SixLabors.ImageSharp
/// <param name="data">The byte array containing image data.</param> /// <param name="data">The byte array containing image data.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception> /// <exception cref="ArgumentNullException">The data is null.</exception>
/// <returns>A new <see cref="Image{Rgba32}"/>.</returns> /// <returns>A new <see cref="Image"/>.</returns>
public static Image<Rgba32> Load(byte[] data) public static Image Load(byte[] data)
=> Load<Rgba32>(Configuration.Default, data); => Load(Configuration.Default, data);
/// <summary> /// <summary>
/// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte array. /// Load a new instance of <see cref="Image{TPixel}"/> from the given encoded byte array.

10
src/ImageSharp/ImageSharp.csproj

@ -12,6 +12,7 @@
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
<PackageTags>Image Resize Crop Gif Jpg Jpeg Bitmap Png Tga NetCore</PackageTags> <PackageTags>Image Resize Crop Gif Jpg Jpeg Bitmap Png Tga NetCore</PackageTags>
<Description>A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET</Description> <Description>A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET</Description>
<Configurations>Debug;Release;Release-InnerLoop;Debug-InnerLoop</Configurations>
</PropertyGroup> </PropertyGroup>
<Choose> <Choose>
@ -20,6 +21,11 @@
<TargetFrameworks>net5.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472</TargetFrameworks> <TargetFrameworks>net5.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
</When> </When>
<When Condition="$(Configuration.EndsWith('InnerLoop')) == true">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
</PropertyGroup>
</When>
<Otherwise> <Otherwise>
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472</TargetFrameworks> <TargetFrameworks>netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472</TargetFrameworks>
@ -31,8 +37,8 @@
<None Include="..\..\shared-infrastructure\branding\icons\imagesharp\sixlabors.imagesharp.128.png" Pack="true" PackagePath="" /> <None Include="..\..\shared-infrastructure\branding\icons\imagesharp\sixlabors.imagesharp.128.png" Pack="true" PackagePath="" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' "> <ItemGroup>
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" /> <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" $(TargetFramework.StartsWith('netstandard')) OR '$(TargetFramework)' == 'net472'"> <ItemGroup Condition=" $(TargetFramework.StartsWith('netstandard')) OR '$(TargetFramework)' == 'net472'">

5
src/ImageSharp/Processing/KnownDitherings.cs

@ -30,6 +30,11 @@ namespace SixLabors.ImageSharp.Processing
/// </summary> /// </summary>
public static IDither Bayer8x8 { get; } = OrderedDither.Bayer8x8; public static IDither Bayer8x8 { get; } = OrderedDither.Bayer8x8;
/// <summary>
/// Gets the order ditherer using the 16x16 Bayer dithering matrix
/// </summary>
public static IDither Bayer16x16 { get; } = OrderedDither.Bayer16x16;
/// <summary> /// <summary>
/// Gets the error Dither that implements the Atkinson algorithm. /// Gets the error Dither that implements the Atkinson algorithm.
/// </summary> /// </summary>

1
src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs

@ -5,7 +5,6 @@ using System;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;

5
src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs

@ -23,6 +23,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// </summary> /// </summary>
public static OrderedDither Bayer8x8 = new OrderedDither(8); public static OrderedDither Bayer8x8 = new OrderedDither(8);
/// <summary>
/// Applies order dithering using the 16x16 Bayer dithering matrix.
/// </summary>
public static OrderedDither Bayer16x16 = new OrderedDither(16);
/// <summary> /// <summary>
/// Applies order dithering using the 3x3 ordered dithering matrix. /// Applies order dithering using the 3x3 ordered dithering matrix.
/// </summary> /// </summary>

57
src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs

@ -3,7 +3,6 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -145,24 +144,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
in ditherOperation); in ditherOperation);
} }
// Spread assumes an even colorspace distribution and precision.
// TODO: Cubed root is currently used to represent 3 color channels
// but we should introduce something to PixelTypeInfo.
// https://bisqwit.iki.fi/story/howto/dither/jy/
// https://en.wikipedia.org/wiki/Ordered_dithering#Algorithm
internal static int CalculatePaletteSpread(int colors)
=> (int)(255 / Math.Max(1, Math.Pow(colors, 1.0 / 3) - 1));
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal TPixel Dither<TPixel>( internal TPixel Dither<TPixel>(
TPixel source, TPixel source,
int x, int x,
int y, int y,
int bitDepth, int spread,
float scale) float scale)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Rgba32 rgba = default; Unsafe.SkipInit(out Rgba32 rgba);
source.ToRgba32(ref rgba); source.ToRgba32(ref rgba);
Rgba32 attempt; Unsafe.SkipInit(out Rgba32 attempt);
// Spread assumes an even colorspace distribution and precision.
// Calculated as 0-255/component count. 256 / bitDepth
// https://bisqwit.iki.fi/story/howto/dither/jy/
// https://en.wikipedia.org/wiki/Ordered_dithering#Algorithm
int spread = 256 / bitDepth;
float factor = spread * this.thresholdMatrix[y % this.modulusY, x % this.modulusX] * scale; float factor = spread * this.thresholdMatrix[y % this.modulusY, x % this.modulusX] * scale;
attempt.R = (byte)Numerics.Clamp(rgba.R + factor, byte.MinValue, byte.MaxValue); attempt.R = (byte)Numerics.Clamp(rgba.R + factor, byte.MinValue, byte.MaxValue);
@ -203,7 +205,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
private readonly ImageFrame<TPixel> source; private readonly ImageFrame<TPixel> source;
private readonly IndexedImageFrame<TPixel> destination; private readonly IndexedImageFrame<TPixel> destination;
private readonly Rectangle bounds; private readonly Rectangle bounds;
private readonly int bitDepth; private readonly int spread;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public QuantizeDitherRowOperation( public QuantizeDitherRowOperation(
@ -218,23 +220,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
this.source = source; this.source = source;
this.destination = destination; this.destination = destination;
this.bounds = bounds; this.bounds = bounds;
this.bitDepth = ColorNumerics.GetBitsNeededForColorDepth(destination.Palette.Length); this.spread = CalculatePaletteSpread(destination.Palette.Length);
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y) public void Invoke(int y)
{ {
int offsetY = this.bounds.Top; ref TFrameQuantizer quantizer = ref Unsafe.AsRef(this.quantizer);
int offsetX = this.bounds.Left; int spread = this.spread;
float scale = this.quantizer.Options.DitherScale; float scale = this.quantizer.Options.DitherScale;
ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); ReadOnlySpan<TPixel> sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
ref byte destinationRowRef = ref MemoryMarshal.GetReference(this.destination.GetWritablePixelRowSpanUnsafe(y - offsetY)); Span<byte> destRow =
this.destination.GetWritablePixelRowSpanUnsafe(y - this.bounds.Y).Slice(0, sourceRow.Length);
for (int x = this.bounds.Left; x < this.bounds.Right; x++) for (int x = 0; x < sourceRow.Length; x++)
{ {
TPixel dithered = this.dither.Dither(Unsafe.Add(ref sourceRowRef, x), x, y, this.bitDepth, scale); TPixel dithered = this.dither.Dither(sourceRow[x], x, y, spread, scale);
Unsafe.Add(ref destinationRowRef, x - offsetX) = Unsafe.AsRef(this.quantizer).GetQuantizedColor(dithered, out TPixel _); destRow[x] = quantizer.GetQuantizedColor(dithered, out TPixel _);
} }
} }
} }
@ -248,7 +251,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
private readonly ImageFrame<TPixel> source; private readonly ImageFrame<TPixel> source;
private readonly Rectangle bounds; private readonly Rectangle bounds;
private readonly float scale; private readonly float scale;
private readonly int bitDepth; private readonly int spread;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public PaletteDitherRowOperation( public PaletteDitherRowOperation(
@ -262,19 +265,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
this.source = source; this.source = source;
this.bounds = bounds; this.bounds = bounds;
this.scale = processor.DitherScale; this.scale = processor.DitherScale;
this.bitDepth = ColorNumerics.GetBitsNeededForColorDepth(processor.Palette.Length); this.spread = CalculatePaletteSpread(processor.Palette.Length);
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y) public void Invoke(int y)
{ {
ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); ref TPaletteDitherImageProcessor processor = ref Unsafe.AsRef(this.processor);
int spread = this.spread;
float scale = this.scale;
Span<TPixel> row = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
for (int x = this.bounds.Left; x < this.bounds.Right; x++) for (int x = 0; x < row.Length; x++)
{ {
ref TPixel sourcePixel = ref Unsafe.Add(ref sourceRowRef, x); ref TPixel sourcePixel = ref row[x];
TPixel dithered = this.dither.Dither(sourcePixel, x, y, this.bitDepth, this.scale); TPixel dithered = this.dither.Dither(sourcePixel, x, y, spread, scale);
sourcePixel = Unsafe.AsRef(this.processor).GetPaletteColor(dithered); sourcePixel = processor.GetPaletteColor(dithered);
} }
} }
} }

3
src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs

@ -72,7 +72,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// Used to allow inlining of calls to /// Used to allow inlining of calls to
/// <see cref="IPaletteDitherImageProcessor{TPixel}.GetPaletteColor(TPixel)"/>. /// <see cref="IPaletteDitherImageProcessor{TPixel}.GetPaletteColor(TPixel)"/>.
/// </summary> /// </summary>
private readonly struct DitherProcessor : IPaletteDitherImageProcessor<TPixel> /// <remarks>Internal for AOT</remarks>
internal readonly struct DitherProcessor : IPaletteDitherImageProcessor<TPixel>
{ {
private readonly EuclideanPixelMap<TPixel> pixelMap; private readonly EuclideanPixelMap<TPixel> pixelMap;

210
src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -80,32 +82,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return; return;
} }
int yRadius = LinearTransformUtils.GetSamplingRadius(in sampler, source.Height, destination.Height);
int xRadius = LinearTransformUtils.GetSamplingRadius(in sampler, source.Width, destination.Width);
var radialExtents = new Vector2(xRadius, yRadius);
int yLength = (yRadius * 2) + 1;
int xLength = (xRadius * 2) + 1;
// We use 2D buffers so that we can access the weight spans per row in parallel.
using Buffer2D<float> yKernelBuffer = configuration.MemoryAllocator.Allocate2D<float>(yLength, destination.Height);
using Buffer2D<float> xKernelBuffer = configuration.MemoryAllocator.Allocate2D<float>(xLength, destination.Height);
int maxX = source.Width - 1;
int maxY = source.Height - 1;
var maxSourceExtents = new Vector4(maxX, maxY, maxX, maxY);
var operation = new AffineOperation<TResampler>( var operation = new AffineOperation<TResampler>(
configuration, configuration,
source, source,
destination, destination,
yKernelBuffer,
xKernelBuffer,
in sampler, in sampler,
matrix, matrix);
radialExtents,
maxSourceExtents);
ParallelRowIterator.IterateRows<AffineOperation<TResampler>, Vector4>( ParallelRowIterator.IterateRowIntervals<AffineOperation<TResampler>, Vector4>(
configuration, configuration,
destination.Bounds(), destination.Bounds(),
in operation); in operation);
@ -117,7 +101,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly ImageFrame<TPixel> destination; private readonly ImageFrame<TPixel> destination;
private readonly Rectangle bounds; private readonly Rectangle bounds;
private readonly Matrix3x2 matrix; private readonly Matrix3x2 matrix;
private readonly int maxX;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public NNAffineOperation( public NNAffineOperation(
@ -129,15 +112,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.destination = destination; this.destination = destination;
this.bounds = source.Bounds(); this.bounds = source.Bounds();
this.matrix = matrix; this.matrix = matrix;
this.maxX = destination.Width;
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y) public void Invoke(int y)
{ {
Buffer2D<TPixel> sourceBuffer = this.source.PixelBuffer;
Span<TPixel> destRow = this.destination.GetPixelRowSpan(y); Span<TPixel> destRow = this.destination.GetPixelRowSpan(y);
for (int x = 0; x < this.maxX; x++) for (int x = 0; x < destRow.Length; x++)
{ {
var point = Vector2.Transform(new Vector2(x, y), this.matrix); var point = Vector2.Transform(new Vector2(x, y), this.matrix);
int px = (int)MathF.Round(point.X); int px = (int)MathF.Round(point.X);
@ -145,84 +128,181 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
if (this.bounds.Contains(px, py)) if (this.bounds.Contains(px, py))
{ {
destRow[x] = this.source[px, py]; destRow[x] = sourceBuffer.GetElementUnsafe(px, py);
} }
} }
} }
} }
private readonly struct AffineOperation<TResampler> : IRowOperation<Vector4> private readonly struct AffineOperation<TResampler> : IRowIntervalOperation<Vector4>
where TResampler : struct, IResampler where TResampler : struct, IResampler
{ {
private readonly Configuration configuration; private readonly Configuration configuration;
private readonly ImageFrame<TPixel> source; private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel> destination; private readonly ImageFrame<TPixel> destination;
private readonly Buffer2D<float> yKernelBuffer;
private readonly Buffer2D<float> xKernelBuffer;
private readonly TResampler sampler; private readonly TResampler sampler;
private readonly Matrix3x2 matrix; private readonly Matrix3x2 matrix;
private readonly Vector2 radialExtents; private readonly float yRadius;
private readonly Vector4 maxSourceExtents; private readonly float xRadius;
private readonly int maxX;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public AffineOperation( public AffineOperation(
Configuration configuration, Configuration configuration,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
ImageFrame<TPixel> destination, ImageFrame<TPixel> destination,
Buffer2D<float> yKernelBuffer,
Buffer2D<float> xKernelBuffer,
in TResampler sampler, in TResampler sampler,
Matrix3x2 matrix, Matrix3x2 matrix)
Vector2 radialExtents,
Vector4 maxSourceExtents)
{ {
this.configuration = configuration; this.configuration = configuration;
this.source = source; this.source = source;
this.destination = destination; this.destination = destination;
this.yKernelBuffer = yKernelBuffer;
this.xKernelBuffer = xKernelBuffer;
this.sampler = sampler; this.sampler = sampler;
this.matrix = matrix; this.matrix = matrix;
this.radialExtents = radialExtents;
this.maxSourceExtents = maxSourceExtents; this.yRadius = LinearTransformUtility.GetSamplingRadius(in sampler, source.Height, destination.Height);
this.maxX = destination.Width; this.xRadius = LinearTransformUtility.GetSamplingRadius(in sampler, source.Width, destination.Width);
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span) public void Invoke(in RowInterval rows, Span<Vector4> span)
{ {
Buffer2D<TPixel> sourceBuffer = this.source.PixelBuffer; if (RuntimeEnvironment.IsOSPlatform(OSPlatform.OSX)
&& RuntimeEnvironment.IsNetCore)
{
// There's something wrong with the JIT in .NET Core 3.1 on certain
// MacOSX machines so we have to use different pipelines.
// It's:
// - Not reproducable locally
// - Doesn't seem to be triggered by the bulk Numerics.UnPremultiply method but by caller.
// https://github.com/SixLabors/ImageSharp/pull/1591
this.InvokeMacOSX(in rows, span);
return;
}
PixelOperations<TPixel>.Instance.ToVector4( Matrix3x2 matrix = this.matrix;
this.configuration, TResampler sampler = this.sampler;
this.destination.GetPixelRowSpan(y), float yRadius = this.yRadius;
span); float xRadius = this.xRadius;
int maxY = this.source.Height - 1;
int maxX = this.source.Width - 1;
ref float yKernelSpanRef = ref MemoryMarshal.GetReference(this.yKernelBuffer.GetRowSpan(y)); Buffer2D<TPixel> sourceBuffer = this.source.PixelBuffer;
ref float xKernelSpanRef = ref MemoryMarshal.GetReference(this.xKernelBuffer.GetRowSpan(y));
for (int x = 0; x < this.maxX; x++) for (int y = rows.Min; y < rows.Max; y++)
{ {
// Use the single precision position to calculate correct bounding pixels Span<TPixel> rowSpan = this.destination.GetPixelRowSpan(y);
// otherwise we get rogue pixels outside of the bounds. PixelOperations<TPixel>.Instance.ToVector4(
var point = Vector2.Transform(new Vector2(x, y), this.matrix); this.configuration,
LinearTransformUtils.Convolve( rowSpan,
in this.sampler, span,
point, PixelConversionModifiers.Scale);
sourceBuffer,
for (int x = 0; x < span.Length; x++)
{
var point = Vector2.Transform(new Vector2(x, y), matrix);
float pY = point.Y;
float pX = point.X;
int top = LinearTransformUtility.GetRangeStart(yRadius, pY, maxY);
int bottom = LinearTransformUtility.GetRangeEnd(yRadius, pY, maxY);
int left = LinearTransformUtility.GetRangeStart(xRadius, pX, maxX);
int right = LinearTransformUtility.GetRangeEnd(xRadius, pX, maxX);
if (bottom == top || right == left)
{
continue;
}
Vector4 sum = Vector4.Zero;
for (int yK = top; yK <= bottom; yK++)
{
float yWeight = sampler.GetValue(yK - pY);
for (int xK = left; xK <= right; xK++)
{
float xWeight = sampler.GetValue(xK - pX);
Vector4 current = sourceBuffer.GetElementUnsafe(xK, yK).ToScaledVector4();
Numerics.Premultiply(ref current);
sum += current * xWeight * yWeight;
}
}
span[x] = sum;
}
Numerics.UnPremultiply(span);
PixelOperations<TPixel>.Instance.FromVector4Destructive(
this.configuration,
span, span,
x, rowSpan,
ref yKernelSpanRef, PixelConversionModifiers.Scale);
ref xKernelSpanRef,
this.radialExtents,
this.maxSourceExtents);
} }
}
[ExcludeFromCodeCoverage]
[MethodImpl(InliningOptions.ShortMethod)]
private void InvokeMacOSX(in RowInterval rows, Span<Vector4> span)
{
Matrix3x2 matrix = this.matrix;
TResampler sampler = this.sampler;
float yRadius = this.yRadius;
float xRadius = this.xRadius;
int maxY = this.source.Height - 1;
int maxX = this.source.Width - 1;
PixelOperations<TPixel>.Instance.FromVector4Destructive( Buffer2D<TPixel> sourceBuffer = this.source.PixelBuffer;
this.configuration,
span, for (int y = rows.Min; y < rows.Max; y++)
this.destination.GetPixelRowSpan(y)); {
Span<TPixel> rowSpan = this.destination.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToVector4(
this.configuration,
rowSpan,
span,
PixelConversionModifiers.Scale);
for (int x = 0; x < span.Length; x++)
{
var point = Vector2.Transform(new Vector2(x, y), matrix);
float pY = point.Y;
float pX = point.X;
int top = LinearTransformUtility.GetRangeStart(yRadius, pY, maxY);
int bottom = LinearTransformUtility.GetRangeEnd(yRadius, pY, maxY);
int left = LinearTransformUtility.GetRangeStart(xRadius, pX, maxX);
int right = LinearTransformUtility.GetRangeEnd(xRadius, pX, maxX);
if (bottom == top || right == left)
{
continue;
}
Vector4 sum = Vector4.Zero;
for (int yK = top; yK <= bottom; yK++)
{
float yWeight = sampler.GetValue(yK - pY);
for (int xK = left; xK <= right; xK++)
{
float xWeight = sampler.GetValue(xK - pX);
Vector4 current = sourceBuffer.GetElementUnsafe(xK, yK).ToScaledVector4();
Numerics.Premultiply(ref current);
sum += current * xWeight * yWeight;
}
}
Numerics.UnPremultiply(ref sum);
span[x] = sum;
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(
this.configuration,
span,
rowSpan,
PixelConversionModifiers.Scale);
}
} }
} }
} }

60
src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs

@ -0,0 +1,60 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// Utility methods for linear transforms.
/// </summary>
internal static class LinearTransformUtility
{
/// <summary>
/// Returns the sampling radius for the given sampler and dimensions.
/// </summary>
/// <typeparam name="TResampler">The type of resampler.</typeparam>
/// <param name="sampler">The resampler sampler.</param>
/// <param name="sourceSize">The source size.</param>
/// <param name="destinationSize">The destination size.</param>
/// <returns>The <see cref="float"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static float GetSamplingRadius<TResampler>(in TResampler sampler, int sourceSize, int destinationSize)
where TResampler : struct, IResampler
{
float scale = (float)sourceSize / destinationSize;
if (scale < 1F)
{
scale = 1F;
}
return MathF.Ceiling(sampler.Radius * scale);
}
/// <summary>
/// Gets the start position (inclusive) for a sampling range given
/// the radius, center position and max constraint.
/// </summary>
/// <param name="radius">The radius.</param>
/// <param name="center">The center position.</param>
/// <param name="max">The max allowed amouunt.</param>
/// <returns>The <see cref="int"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static int GetRangeStart(float radius, float center, int max)
=> Numerics.Clamp((int)MathF.Ceiling(center - radius), 0, max);
/// <summary>
/// Gets the end position (inclusive) for a sampling range given
/// the radius, center position and max constraint.
/// </summary>
/// <param name="radius">The radius.</param>
/// <param name="center">The center position.</param>
/// <param name="max">The max allowed amouunt.</param>
/// <returns>The <see cref="int"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static int GetRangeEnd(float radius, float center, int max)
=> Numerics.Clamp((int)MathF.Floor(center + radius), 0, max);
}
}

104
src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtils.cs

@ -1,104 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
/// <summary>
/// Utility methods for affine and projective transforms.
/// </summary>
internal static class LinearTransformUtils
{
[MethodImpl(InliningOptions.ShortMethod)]
internal static int GetSamplingRadius<TResampler>(in TResampler sampler, int sourceSize, int destinationSize)
where TResampler : struct, IResampler
{
double scale = sourceSize / destinationSize;
if (scale < 1)
{
scale = 1;
}
return (int)Math.Ceiling(scale * sampler.Radius);
}
[MethodImpl(InliningOptions.ShortMethod)]
internal static void Convolve<TResampler, TPixel>(
in TResampler sampler,
Vector2 transformedPoint,
Buffer2D<TPixel> sourcePixels,
Span<Vector4> targetRow,
int column,
ref float yKernelSpanRef,
ref float xKernelSpanRef,
Vector2 radialExtents,
Vector4 maxSourceExtents)
where TResampler : struct, IResampler
where TPixel : unmanaged, IPixel<TPixel>
{
// Clamp sampling pixel radial extents to the source image edges
Vector2 minXY = transformedPoint - radialExtents;
Vector2 maxXY = transformedPoint + radialExtents;
// left, top, right, bottom
var sourceExtents = new Vector4(
MathF.Ceiling(minXY.X),
MathF.Ceiling(minXY.Y),
MathF.Floor(maxXY.X),
MathF.Floor(maxXY.Y));
sourceExtents = Numerics.Clamp(sourceExtents, Vector4.Zero, maxSourceExtents);
int left = (int)sourceExtents.X;
int top = (int)sourceExtents.Y;
int right = (int)sourceExtents.Z;
int bottom = (int)sourceExtents.W;
if (left == right || top == bottom)
{
return;
}
CalculateWeights(in sampler, top, bottom, transformedPoint.Y, ref yKernelSpanRef);
CalculateWeights(in sampler, left, right, transformedPoint.X, ref xKernelSpanRef);
Vector4 sum = Vector4.Zero;
for (int kernelY = 0, y = top; y <= bottom; y++, kernelY++)
{
float yWeight = Unsafe.Add(ref yKernelSpanRef, kernelY);
for (int kernelX = 0, x = left; x <= right; x++, kernelX++)
{
float xWeight = Unsafe.Add(ref xKernelSpanRef, kernelX);
// Values are first premultiplied to prevent darkening of edge pixels.
var current = sourcePixels[x, y].ToVector4();
Numerics.Premultiply(ref current);
sum += current * xWeight * yWeight;
}
}
// Reverse the premultiplication
Numerics.UnPremultiply(ref sum);
targetRow[column] = sum;
}
[MethodImpl(InliningOptions.ShortMethod)]
private static void CalculateWeights<TResampler>(in TResampler sampler, int min, int max, float point, ref float weightsRef)
where TResampler : struct, IResampler
{
float sum = 0;
for (int x = 0, i = min; i <= max; i++, x++)
{
float weight = sampler.GetValue(i - point);
sum += weight;
Unsafe.Add(ref weightsRef, x) = weight;
}
}
}
}

209
src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -80,32 +81,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return; return;
} }
int yRadius = LinearTransformUtils.GetSamplingRadius(in sampler, source.Height, destination.Height);
int xRadius = LinearTransformUtils.GetSamplingRadius(in sampler, source.Width, destination.Width);
var radialExtents = new Vector2(xRadius, yRadius);
int yLength = (yRadius * 2) + 1;
int xLength = (xRadius * 2) + 1;
// We use 2D buffers so that we can access the weight spans per row in parallel.
using Buffer2D<float> yKernelBuffer = configuration.MemoryAllocator.Allocate2D<float>(yLength, destination.Height);
using Buffer2D<float> xKernelBuffer = configuration.MemoryAllocator.Allocate2D<float>(xLength, destination.Height);
int maxX = source.Width - 1;
int maxY = source.Height - 1;
var maxSourceExtents = new Vector4(maxX, maxY, maxX, maxY);
var operation = new ProjectiveOperation<TResampler>( var operation = new ProjectiveOperation<TResampler>(
configuration, configuration,
source, source,
destination, destination,
yKernelBuffer,
xKernelBuffer,
in sampler, in sampler,
matrix, matrix);
radialExtents,
maxSourceExtents);
ParallelRowIterator.IterateRows<ProjectiveOperation<TResampler>, Vector4>( ParallelRowIterator.IterateRowIntervals<ProjectiveOperation<TResampler>, Vector4>(
configuration, configuration,
destination.Bounds(), destination.Bounds(),
in operation); in operation);
@ -117,7 +100,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly ImageFrame<TPixel> destination; private readonly ImageFrame<TPixel> destination;
private readonly Rectangle bounds; private readonly Rectangle bounds;
private readonly Matrix4x4 matrix; private readonly Matrix4x4 matrix;
private readonly int maxX;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public NNProjectiveOperation( public NNProjectiveOperation(
@ -129,15 +111,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.destination = destination; this.destination = destination;
this.bounds = source.Bounds(); this.bounds = source.Bounds();
this.matrix = matrix; this.matrix = matrix;
this.maxX = destination.Width;
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y) public void Invoke(int y)
{ {
Buffer2D<TPixel> sourceBuffer = this.source.PixelBuffer;
Span<TPixel> destRow = this.destination.GetPixelRowSpan(y); Span<TPixel> destRow = this.destination.GetPixelRowSpan(y);
for (int x = 0; x < this.maxX; x++) for (int x = 0; x < destRow.Length; x++)
{ {
Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix);
int px = (int)MathF.Round(point.X); int px = (int)MathF.Round(point.X);
@ -145,84 +127,181 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
if (this.bounds.Contains(px, py)) if (this.bounds.Contains(px, py))
{ {
destRow[x] = this.source[px, py]; destRow[x] = sourceBuffer.GetElementUnsafe(px, py);
} }
} }
} }
} }
private readonly struct ProjectiveOperation<TResampler> : IRowOperation<Vector4> private readonly struct ProjectiveOperation<TResampler> : IRowIntervalOperation<Vector4>
where TResampler : struct, IResampler where TResampler : struct, IResampler
{ {
private readonly Configuration configuration; private readonly Configuration configuration;
private readonly ImageFrame<TPixel> source; private readonly ImageFrame<TPixel> source;
private readonly ImageFrame<TPixel> destination; private readonly ImageFrame<TPixel> destination;
private readonly Buffer2D<float> yKernelBuffer;
private readonly Buffer2D<float> xKernelBuffer;
private readonly TResampler sampler; private readonly TResampler sampler;
private readonly Matrix4x4 matrix; private readonly Matrix4x4 matrix;
private readonly Vector2 radialExtents; private readonly float yRadius;
private readonly Vector4 maxSourceExtents; private readonly float xRadius;
private readonly int maxX;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public ProjectiveOperation( public ProjectiveOperation(
Configuration configuration, Configuration configuration,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
ImageFrame<TPixel> destination, ImageFrame<TPixel> destination,
Buffer2D<float> yKernelBuffer,
Buffer2D<float> xKernelBuffer,
in TResampler sampler, in TResampler sampler,
Matrix4x4 matrix, Matrix4x4 matrix)
Vector2 radialExtents,
Vector4 maxSourceExtents)
{ {
this.configuration = configuration; this.configuration = configuration;
this.source = source; this.source = source;
this.destination = destination; this.destination = destination;
this.yKernelBuffer = yKernelBuffer;
this.xKernelBuffer = xKernelBuffer;
this.sampler = sampler; this.sampler = sampler;
this.matrix = matrix; this.matrix = matrix;
this.radialExtents = radialExtents;
this.maxSourceExtents = maxSourceExtents; this.yRadius = LinearTransformUtility.GetSamplingRadius(in sampler, source.Height, destination.Height);
this.maxX = destination.Width; this.xRadius = LinearTransformUtility.GetSamplingRadius(in sampler, source.Width, destination.Width);
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y, Span<Vector4> span) public void Invoke(in RowInterval rows, Span<Vector4> span)
{ {
Buffer2D<TPixel> sourceBuffer = this.source.PixelBuffer; if (RuntimeEnvironment.IsOSPlatform(OSPlatform.OSX)
&& RuntimeEnvironment.IsNetCore)
{
// There's something wrong with the JIT in .NET Core 3.1 on certain
// MacOSX machines so we have to use different pipelines.
// It's:
// - Not reproducable locally
// - Doesn't seem to be triggered by the bulk Numerics.UnPremultiply method but by caller.
// https://github.com/SixLabors/ImageSharp/pull/1591
this.InvokeMacOSX(in rows, span);
return;
}
PixelOperations<TPixel>.Instance.ToVector4( Matrix4x4 matrix = this.matrix;
this.configuration, TResampler sampler = this.sampler;
this.destination.GetPixelRowSpan(y), float yRadius = this.yRadius;
span); float xRadius = this.xRadius;
int maxY = this.source.Height - 1;
int maxX = this.source.Width - 1;
ref float yKernelSpanRef = ref MemoryMarshal.GetReference(this.yKernelBuffer.GetRowSpan(y)); Buffer2D<TPixel> sourceBuffer = this.source.PixelBuffer;
ref float xKernelSpanRef = ref MemoryMarshal.GetReference(this.xKernelBuffer.GetRowSpan(y));
for (int x = 0; x < this.maxX; x++) for (int y = rows.Min; y < rows.Max; y++)
{ {
// Use the single precision position to calculate correct bounding pixels Span<TPixel> rowSpan = this.destination.GetPixelRowSpan(y);
// otherwise we get rogue pixels outside of the bounds. PixelOperations<TPixel>.Instance.ToVector4(
Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); this.configuration,
LinearTransformUtils.Convolve( rowSpan,
in this.sampler, span,
point, PixelConversionModifiers.Scale);
sourceBuffer,
for (int x = 0; x < span.Length; x++)
{
Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix);
float pY = point.Y;
float pX = point.X;
int top = LinearTransformUtility.GetRangeStart(yRadius, pY, maxY);
int bottom = LinearTransformUtility.GetRangeEnd(yRadius, pY, maxY);
int left = LinearTransformUtility.GetRangeStart(xRadius, pX, maxX);
int right = LinearTransformUtility.GetRangeEnd(xRadius, pX, maxX);
if (bottom <= top || right <= left)
{
continue;
}
Vector4 sum = Vector4.Zero;
for (int yK = top; yK <= bottom; yK++)
{
float yWeight = sampler.GetValue(yK - pY);
for (int xK = left; xK <= right; xK++)
{
float xWeight = sampler.GetValue(xK - pX);
Vector4 current = sourceBuffer.GetElementUnsafe(xK, yK).ToScaledVector4();
Numerics.Premultiply(ref current);
sum += current * xWeight * yWeight;
}
}
span[x] = sum;
}
Numerics.UnPremultiply(span);
PixelOperations<TPixel>.Instance.FromVector4Destructive(
this.configuration,
span, span,
x, rowSpan,
ref yKernelSpanRef, PixelConversionModifiers.Scale);
ref xKernelSpanRef,
this.radialExtents,
this.maxSourceExtents);
} }
}
[ExcludeFromCodeCoverage]
[MethodImpl(InliningOptions.ShortMethod)]
public void InvokeMacOSX(in RowInterval rows, Span<Vector4> span)
{
Matrix4x4 matrix = this.matrix;
TResampler sampler = this.sampler;
float yRadius = this.yRadius;
float xRadius = this.xRadius;
int maxY = this.source.Height - 1;
int maxX = this.source.Width - 1;
PixelOperations<TPixel>.Instance.FromVector4Destructive( Buffer2D<TPixel> sourceBuffer = this.source.PixelBuffer;
this.configuration,
span, for (int y = rows.Min; y < rows.Max; y++)
this.destination.GetPixelRowSpan(y)); {
Span<TPixel> rowSpan = this.destination.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToVector4(
this.configuration,
rowSpan,
span,
PixelConversionModifiers.Scale);
for (int x = 0; x < span.Length; x++)
{
Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix);
float pY = point.Y;
float pX = point.X;
int top = LinearTransformUtility.GetRangeStart(yRadius, pY, maxY);
int bottom = LinearTransformUtility.GetRangeEnd(yRadius, pY, maxY);
int left = LinearTransformUtility.GetRangeStart(xRadius, pX, maxX);
int right = LinearTransformUtility.GetRangeEnd(xRadius, pX, maxX);
if (bottom <= top || right <= left)
{
continue;
}
Vector4 sum = Vector4.Zero;
for (int yK = top; yK <= bottom; yK++)
{
float yWeight = sampler.GetValue(yK - pY);
for (int xK = left; xK <= right; xK++)
{
float xWeight = sampler.GetValue(xK - pX);
Vector4 current = sourceBuffer.GetElementUnsafe(xK, yK).ToScaledVector4();
Numerics.Premultiply(ref current);
sum += current * xWeight * yWeight;
}
}
Numerics.UnPremultiply(ref sum);
span[x] = sum;
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(
this.configuration,
span,
rowSpan,
PixelConversionModifiers.Scale);
}
} }
} }
} }

4
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs

@ -13,7 +13,7 @@ using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
/// <summary> /// <summary>
/// Points to a collection of of weights allocated in <see cref="ResizeKernelMap"/>. /// Points to a collection of weights allocated in <see cref="ResizeKernelMap"/>.
/// </summary> /// </summary>
internal readonly unsafe struct ResizeKernel internal readonly unsafe struct ResizeKernel
{ {
@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
} }
/// <summary> /// <summary>
/// Gets the the length of the kernel. /// Gets the length of the kernel.
/// </summary> /// </summary>
public int Length public int Length
{ {

2
tests/Directory.Build.targets

@ -21,7 +21,7 @@
<PackageReference Update="BenchmarkDotNet" Version="0.12.1" /> <PackageReference Update="BenchmarkDotNet" Version="0.12.1" />
<PackageReference Update="BenchmarkDotNet.Diagnostics.Windows" Version="0.12.1" Condition="'$(IsWindows)'=='true'" /> <PackageReference Update="BenchmarkDotNet.Diagnostics.Windows" Version="0.12.1" Condition="'$(IsWindows)'=='true'" />
<PackageReference Update="Colourful" Version="2.0.5" /> <PackageReference Update="Colourful" Version="2.0.5" />
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="7.22.0" /> <PackageReference Update="Magick.NET-Q16-AnyCPU" Version="7.23.2.1" />
<PackageReference Update="Microsoft.DotNet.RemoteExecutor" Version="6.0.0-beta.20513.1" /> <PackageReference Update="Microsoft.DotNet.RemoteExecutor" Version="6.0.0-beta.20513.1" />
<PackageReference Update="Microsoft.DotNet.XUnitExtensions" Version="6.0.0-beta.20513.1" /> <PackageReference Update="Microsoft.DotNet.XUnitExtensions" Version="6.0.0-beta.20513.1" />
<PackageReference Update="Moq" Version="4.14.6" /> <PackageReference Update="Moq" Version="4.14.6" />

15
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

@ -5,15 +5,28 @@
<AssemblyName>ImageSharp.Benchmarks</AssemblyName> <AssemblyName>ImageSharp.Benchmarks</AssemblyName>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RootNamespace>SixLabors.ImageSharp.Benchmarks</RootNamespace> <RootNamespace>SixLabors.ImageSharp.Benchmarks</RootNamespace>
<TargetFrameworks>net5.0;netcoreapp3.1;netcoreapp2.1;net472</TargetFrameworks>
<GenerateProgramFile>false</GenerateProgramFile> <GenerateProgramFile>false</GenerateProgramFile>
<!--Used to hide test project from dotnet test--> <!--Used to hide test project from dotnet test-->
<IsTestProject>false</IsTestProject> <IsTestProject>false</IsTestProject>
<Configurations>Debug;Release;Release-InnerLoop;Debug-InnerLoop</Configurations>
<!-- Uncomment this to run benchmarks depending on Colorful or Pfim (colorspaces and TGA): --> <!-- Uncomment this to run benchmarks depending on Colorful or Pfim (colorspaces and TGA): -->
<!--<SignAssembly>false</SignAssembly>--> <!--<SignAssembly>false</SignAssembly>-->
</PropertyGroup> </PropertyGroup>
<Choose>
<When Condition="$(Configuration.EndsWith('InnerLoop')) == true">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<TargetFrameworks>net5.0;netcoreapp3.1;netcoreapp2.1;net472</TargetFrameworks>
</PropertyGroup>
</Otherwise>
</Choose>
<ItemGroup> <ItemGroup>
<Compile Include="..\ImageSharp.Tests\TestImages.cs" Link="Tests\TestImages.cs" /> <Compile Include="..\ImageSharp.Tests\TestImages.cs" Link="Tests\TestImages.cs" />
<Compile Include="..\ImageSharp.Tests\TestUtilities\TestEnvironment.cs" Link="Tests\TestEnvironment.cs" /> <Compile Include="..\ImageSharp.Tests\TestUtilities\TestEnvironment.cs" Link="Tests\TestEnvironment.cs" />

28
tests/ImageSharp.Benchmarks/Processing/Rotate.cs

@ -21,21 +21,19 @@ namespace SixLabors.ImageSharp.Benchmarks.Processing
} }
} }
// #### 21th February 2020 #### // #### 2021-04-06 ####
// //
// BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363 // BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
// Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores // Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
// .NET Core SDK = 3.1.101 // .NET Core SDK=5.0.201
// [Host] : .NET Core 5.0.4 (CoreCLR 5.0.421.11614, CoreFX 5.0.421.11614), X64 RyuJIT
// Job-HQWHDJ : .NET Framework 4.8 (4.8.4341.0), X64 RyuJIT
// Job-RPXLFC : .NET Core 2.1.26 (CoreCLR 4.6.29812.02, CoreFX 4.6.29812.01), X64 RyuJIT
// Job-YMSKIM : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT
// //
// [Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
// Job-HOGSNT : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT
// Job-FKDHXC : .NET Core 2.1.15 (CoreCLR 4.6.28325.01, CoreFX 4.6.28327.02), X64 RyuJIT
// Job-ODABAZ : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
// //
// IterationCount=3 LaunchCount=1 WarmupCount=3 // | Method | Job | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
// // |--------- |----------- |-------------- |---------:|---------:|---------:|------:|------:|------:|----------:|
// | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | // | DoRotate | Job-BAUEPW | .NET 4.7.2 | 30.73 ms | 0.397 ms | 0.331 ms | - | - | - | 6.75 KB |
// |--------- |-------------- |---------:|---------:|---------:|------:|------:|------:|----------:| // | DoRotate | Job-SNWMCN | .NET Core 2.1 | 16.31 ms | 0.317 ms | 0.352 ms | - | - | - | 5.25 KB |
// | DoRotate | .NET 4.7.2 | 28.77 ms | 3.304 ms | 0.181 ms | - | - | - | 6.5 KB | // | DoRotate | Job-MRMBJZ | .NET Core 3.1 | 12.21 ms | 0.239 ms | 0.245 ms | - | - | - | 6.61 KB |
// | DoRotate | .NET Core 2.1 | 16.27 ms | 1.044 ms | 0.057 ms | - | - | - | 5.25 KB |
// | DoRotate | .NET Core 3.1 | 17.12 ms | 4.352 ms | 0.239 ms | - | - | - | 6.57 KB |

28
tests/ImageSharp.Benchmarks/Processing/Skew.cs

@ -21,21 +21,19 @@ namespace SixLabors.ImageSharp.Benchmarks.Processing
} }
} }
// #### 21th February 2020 #### // #### 2021-04-06 ####
// //
// BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363 // BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
// Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores // Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
// .NET Core SDK = 3.1.101 // .NET Core SDK=5.0.201
// [Host] : .NET Core 5.0.4 (CoreCLR 5.0.421.11614, CoreFX 5.0.421.11614), X64 RyuJIT
// Job-HQWHDJ : .NET Framework 4.8 (4.8.4341.0), X64 RyuJIT
// Job-RPXLFC : .NET Core 2.1.26 (CoreCLR 4.6.29812.02, CoreFX 4.6.29812.01), X64 RyuJIT
// Job-YMSKIM : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT
// //
// [Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
// Job-VKKTMF : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT
// Job-KTVRKR : .NET Core 2.1.15 (CoreCLR 4.6.28325.01, CoreFX 4.6.28327.02), X64 RyuJIT
// Job-EONWDB : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
// //
// IterationCount=3 LaunchCount=1 WarmupCount=3 // | Method | Job | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
// // |------- |----------- |-------------- |----------:|----------:|----------:|------:|------:|------:|----------:|
// | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | // | DoSkew | Job-YEGFRQ | .NET 4.7.2 | 23.563 ms | 0.0731 ms | 0.0570 ms | - | - | - | 6.75 KB |
// |------- |-------------- |---------:|----------:|---------:|------:|------:|------:|----------:| // | DoSkew | Job-HZHOGR | .NET Core 2.1 | 13.700 ms | 0.2727 ms | 0.5122 ms | - | - | - | 5.25 KB |
// | DoSkew | .NET 4.7.2 | 24.60 ms | 33.971 ms | 1.862 ms | - | - | - | 6.5 KB | // | DoSkew | Job-LTEUKY | .NET Core 3.1 | 9.971 ms | 0.0254 ms | 0.0225 ms | - | - | - | 6.61 KB |
// | DoSkew | .NET Core 2.1 | 12.13 ms | 2.256 ms | 0.124 ms | - | - | - | 5.21 KB |
// | DoSkew | .NET Core 3.1 | 12.83 ms | 1.442 ms | 0.079 ms | - | - | - | 6.57 KB |

15
tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj

@ -8,13 +8,26 @@
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<RootNamespace>SixLabors.ImageSharp.Tests.ProfilingSandbox</RootNamespace> <RootNamespace>SixLabors.ImageSharp.Tests.ProfilingSandbox</RootNamespace>
<RuntimeIdentifier>win7-x64</RuntimeIdentifier> <RuntimeIdentifier>win7-x64</RuntimeIdentifier>
<TargetFrameworks>net5.0;netcoreapp3.1;netcoreapp2.1;net472</TargetFrameworks>
<StartupObject>SixLabors.ImageSharp.Tests.ProfilingSandbox.Program</StartupObject> <StartupObject>SixLabors.ImageSharp.Tests.ProfilingSandbox.Program</StartupObject>
<!--Used to hide test project from dotnet test--> <!--Used to hide test project from dotnet test-->
<IsTestProject>false</IsTestProject> <IsTestProject>false</IsTestProject>
<EnsureNETCoreAppRuntime>false</EnsureNETCoreAppRuntime> <EnsureNETCoreAppRuntime>false</EnsureNETCoreAppRuntime>
<Configurations>Debug;Release;Release-InnerLoop;Debug-InnerLoop</Configurations>
</PropertyGroup> </PropertyGroup>
<Choose>
<When Condition="$(Configuration.EndsWith('InnerLoop')) == true">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<TargetFrameworks>net5.0;netcoreapp3.1;netcoreapp2.1;net472</TargetFrameworks>
</PropertyGroup>
</Otherwise>
</Choose>
<ItemGroup> <ItemGroup>
<Compile Remove="..\ImageSharp.Tests\IO\LocalFileSystem.cs" /> <Compile Remove="..\ImageSharp.Tests\IO\LocalFileSystem.cs" />
</ItemGroup> </ItemGroup>

6
tests/ImageSharp.Tests.ProfilingSandbox/app.config

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<runtime> <runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
@ -12,8 +12,8 @@
</dependentAssembly> </dependentAssembly>
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" /> <bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
</dependentAssembly> </dependentAssembly>
</assemblyBinding> </assemblyBinding>
</runtime> </runtime>
</configuration> </configuration>

4
tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs

@ -155,9 +155,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
[Fact] [Fact]
public void CanDecodeIntermingledImages() public void CanDecodeIntermingledImages()
{ {
using (var kumin1 = Image.Load(TestFile.Create(TestImages.Gif.Kumin).Bytes)) using (var kumin1 = Image.Load<Rgba32>(TestFile.Create(TestImages.Gif.Kumin).Bytes))
using (Image.Load(TestFile.Create(TestImages.Png.Icon).Bytes)) using (Image.Load(TestFile.Create(TestImages.Png.Icon).Bytes))
using (var kumin2 = Image.Load(TestFile.Create(TestImages.Gif.Kumin).Bytes)) using (var kumin2 = Image.Load<Rgba32>(TestFile.Create(TestImages.Gif.Kumin).Bytes))
{ {
for (int i = 0; i < kumin1.Frames.Count; i++) for (int i = 0; i < kumin1.Frames.Count; i++)
{ {

31
tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs

@ -40,6 +40,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{ JpegSubsample.Ratio444, 100 }, { JpegSubsample.Ratio444, 100 },
}; };
public static readonly TheoryData<int> Grayscale_Quality =
new TheoryData<int>
{
{ 40 },
{ 60 },
{ 100 }
};
public static readonly TheoryData<string, int, int, PixelResolutionUnit> RatioFiles = public static readonly TheoryData<string, int, int, PixelResolutionUnit> RatioFiles =
new TheoryData<string, int, int, PixelResolutionUnit> new TheoryData<string, int, int, PixelResolutionUnit>
{ {
@ -80,9 +88,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] [WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)]
[WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 100, 100, 100, 255, PixelTypes.L8)]
public void EncodeBaseline_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegSubsample subsample, int quality) public void EncodeBaseline_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegSubsample subsample, int quality)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, subsample, quality); where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, subsample, quality);
[Theory]
[WithFile(TestImages.Png.BikeGrayscale, nameof(Grayscale_Quality), PixelTypes.L8)]
[WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.Rgba32, 100)]
[WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.L8, 100)]
[WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.L16, 100)]
[WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La16, 100)]
[WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La32, 100)]
public void EncodeBaseline_Grayscale<TPixel>(TestImageProvider<TPixel> provider, int quality)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, null, quality, JpegColorType.Luminance);
[Theory] [Theory]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)]
public void EncodeBaseline_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, JpegSubsample subsample, int quality) public void EncodeBaseline_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, JpegSubsample subsample, int quality)
@ -101,13 +120,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
: ImageComparer.TolerantPercentage(5f); : ImageComparer.TolerantPercentage(5f);
provider.LimitAllocatorBufferCapacity().InBytesSqrt(200); provider.LimitAllocatorBufferCapacity().InBytesSqrt(200);
TestJpegEncoderCore(provider, subsample, 100, comparer); TestJpegEncoderCore(provider, subsample, 100, JpegColorType.YCbCr, comparer);
} }
/// <summary> /// <summary>
/// Anton's SUPER-SCIENTIFIC tolerance threshold calculation /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation
/// </summary> /// </summary>
private static ImageComparer GetComparer(int quality, JpegSubsample subsample) private static ImageComparer GetComparer(int quality, JpegSubsample? subsample)
{ {
float tolerance = 0.015f; // ~1.5% float tolerance = 0.015f; // ~1.5%
@ -129,8 +148,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
private static void TestJpegEncoderCore<TPixel>( private static void TestJpegEncoderCore<TPixel>(
TestImageProvider<TPixel> provider, TestImageProvider<TPixel> provider,
JpegSubsample subsample, JpegSubsample? subsample,
int quality = 100, int quality = 100,
JpegColorType colorType = JpegColorType.YCbCr,
ImageComparer comparer = null) ImageComparer comparer = null)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
@ -142,7 +162,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var encoder = new JpegEncoder var encoder = new JpegEncoder
{ {
Subsample = subsample, Subsample = subsample,
Quality = quality Quality = quality,
ColorType = colorType
}; };
string info = $"{subsample}-Q{quality}"; string info = $"{subsample}-Q{quality}";
@ -298,7 +319,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public async Task Encode_IsCancellable(JpegSubsample subsample, int cancellationDelayMs) public async Task Encode_IsCancellable(JpegSubsample subsample, int cancellationDelayMs)
{ {
using var image = new Image<Rgba32>(5000, 5000); using var image = new Image<Rgba32>(5000, 5000);
using MemoryStream stream = new MemoryStream(); using var stream = new MemoryStream();
var cts = new CancellationTokenSource(); var cts = new CancellationTokenSource();
if (cancellationDelayMs == 0) if (cancellationDelayMs == 0)
{ {

4
tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs

@ -12,12 +12,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Fact] [Fact]
public void CloneIsDeep() public void CloneIsDeep()
{ {
var meta = new JpegMetadata { Quality = 50 }; var meta = new JpegMetadata { Quality = 50, ColorType = JpegColorType.Luminance };
var clone = (JpegMetadata)meta.DeepClone(); var clone = (JpegMetadata)meta.DeepClone();
clone.Quality = 99; clone.Quality = 99;
clone.ColorType = JpegColorType.YCbCr;
Assert.False(meta.Quality.Equals(clone.Quality)); Assert.False(meta.Quality.Equals(clone.Quality));
Assert.False(meta.ColorType.Equals(clone.ColorType));
} }
} }
} }

41
tests/ImageSharp.Tests/Helpers/RuntimeEnvironmentTests.cs

@ -0,0 +1,41 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.InteropServices;
using Xunit;
#pragma warning disable IDE0022 // Use expression body for methods
namespace SixLabors.ImageSharp.Tests.Helpers
{
public class RuntimeEnvironmentTests
{
[Fact]
public void CanDetectNetCore()
{
#if NET5_0_OR_GREATER
Assert.False(RuntimeEnvironment.IsNetCore);
#elif NETCOREAPP
Assert.True(RuntimeEnvironment.IsNetCore);
#else
Assert.False(RuntimeEnvironment.IsNetCore);
#endif
}
[Fact]
public void CanDetectOSPlatform()
{
if (TestEnvironment.IsLinux)
{
Assert.True(RuntimeEnvironment.IsOSPlatform(OSPlatform.Linux));
}
else if (TestEnvironment.IsOSX)
{
Assert.True(RuntimeEnvironment.IsOSPlatform(OSPlatform.OSX));
}
else if (TestEnvironment.IsWindows)
{
Assert.True(RuntimeEnvironment.IsOSPlatform(OSPlatform.Windows));
}
}
}
}

15
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -2,13 +2,26 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net5.0;netcoreapp3.1;netcoreapp2.1;net472</TargetFrameworks>
<DebugSymbols>True</DebugSymbols> <DebugSymbols>True</DebugSymbols>
<AssemblyName>SixLabors.ImageSharp.Tests</AssemblyName> <AssemblyName>SixLabors.ImageSharp.Tests</AssemblyName>
<Platforms>AnyCPU;x64;x86</Platforms> <Platforms>AnyCPU;x64;x86</Platforms>
<RootNamespace>SixLabors.ImageSharp.Tests</RootNamespace> <RootNamespace>SixLabors.ImageSharp.Tests</RootNamespace>
<Configurations>Debug;Release;Release-InnerLoop;Debug-InnerLoop</Configurations>
</PropertyGroup> </PropertyGroup>
<Choose>
<When Condition="$(Configuration.EndsWith('InnerLoop')) == true">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<TargetFrameworks>net5.0;netcoreapp3.1;netcoreapp2.1;net472</TargetFrameworks>
</PropertyGroup>
</Otherwise>
</Choose>
<ItemGroup> <ItemGroup>
<InternalsVisibleTo Include="ImageSharp.Tests.ProfilingSandbox" Key="$(SixLaborsPublicKey)" /> <InternalsVisibleTo Include="ImageSharp.Tests.ProfilingSandbox" Key="$(SixLaborsPublicKey)" />
</ItemGroup> </ItemGroup>

1
tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs

@ -37,6 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization
{ KnownDitherings.Bayer2x2, nameof(KnownDitherings.Bayer2x2) }, { KnownDitherings.Bayer2x2, nameof(KnownDitherings.Bayer2x2) },
{ KnownDitherings.Bayer4x4, nameof(KnownDitherings.Bayer4x4) }, { KnownDitherings.Bayer4x4, nameof(KnownDitherings.Bayer4x4) },
{ KnownDitherings.Bayer8x8, nameof(KnownDitherings.Bayer8x8) }, { KnownDitherings.Bayer8x8, nameof(KnownDitherings.Bayer8x8) },
{ KnownDitherings.Bayer16x16, nameof(KnownDitherings.Bayer16x16) },
{ KnownDitherings.Ordered3x3, nameof(KnownDitherings.Ordered3x3) } { KnownDitherings.Ordered3x3, nameof(KnownDitherings.Ordered3x3) }
}; };

2
tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs

@ -169,8 +169,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
provider.RunRectangleConstrainedValidatingProcessorTest( provider.RunRectangleConstrainedValidatingProcessorTest(
(x, rect) => x.Quantize(quantizer, rect), (x, rect) => x.Quantize(quantizer, rect),
comparer: ValidatorComparer,
testOutputDetails: testOutputDetails, testOutputDetails: testOutputDetails,
comparer: ValidatorComparer,
appendPixelTypeToFileName: false); appendPixelTypeToFileName: false);
} }

5
tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs

@ -4,7 +4,6 @@
using System; using System;
using System.Numerics; using System.Numerics;
using System.Reflection; using System.Reflection;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Processing.Processors.Transforms;
@ -18,9 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
public class AffineTransformTests public class AffineTransformTests
{ {
private readonly ITestOutputHelper output; private readonly ITestOutputHelper output;
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.033F, 3);
// 1 byte difference on one color component.
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0134F, 3);
/// <summary> /// <summary>
/// angleDeg, sx, sy, tx, ty /// angleDeg, sx, sy, tx, ty

2
tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs

@ -174,7 +174,7 @@ namespace SixLabors.ImageSharp.Tests
appendPixelTypeToFileName, appendPixelTypeToFileName,
appendSourceFileOrDescription); appendSourceFileOrDescription);
encoder = encoder ?? TestEnvironment.GetReferenceEncoder(path); encoder ??= TestEnvironment.GetReferenceEncoder(path);
using (FileStream stream = File.OpenWrite(path)) using (FileStream stream = File.OpenWrite(path))
{ {

94
tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/ImageSharpPngEncoderWithDefaultConfiguration.cs

@ -0,0 +1,94 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
{
/// <summary>
/// A Png encoder that uses the ImageSharp core encoder but the default configuration.
/// This allows encoding under environments with restricted memory.
/// </summary>
public sealed class ImageSharpPngEncoderWithDefaultConfiguration : IImageEncoder, IPngEncoderOptions
{
/// <inheritdoc/>
public PngBitDepth? BitDepth { get; set; }
/// <inheritdoc/>
public PngColorType? ColorType { get; set; }
/// <inheritdoc/>
public PngFilterMethod? FilterMethod { get; set; }
/// <inheritdoc/>
public PngCompressionLevel CompressionLevel { get; set; } = PngCompressionLevel.DefaultCompression;
/// <inheritdoc/>
public int TextCompressionThreshold { get; set; } = 1024;
/// <inheritdoc/>
public float? Gamma { get; set; }
/// <inheritdoc/>
public IQuantizer Quantizer { get; set; }
/// <inheritdoc/>
public byte Threshold { get; set; } = byte.MaxValue;
/// <inheritdoc/>
public PngInterlaceMode? InterlaceMethod { get; set; }
/// <inheritdoc/>
public PngChunkFilter? ChunkFilter { get; set; }
/// <inheritdoc/>
public bool IgnoreMetadata { get; set; }
/// <inheritdoc/>
public PngTransparentColorMode TransparentColorMode { get; set; }
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
Configuration configuration = Configuration.Default;
MemoryAllocator allocator = configuration.MemoryAllocator;
using var encoder = new PngEncoderCore(allocator, configuration, new PngEncoderOptions(this));
encoder.Encode(image, stream);
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Configuration configuration = Configuration.Default;
MemoryAllocator allocator = configuration.MemoryAllocator;
// The introduction of a local variable that refers to an object the implements
// IDisposable means you must use async/await, where the compiler generates the
// state machine and a continuation.
using var encoder = new PngEncoderCore(allocator, configuration, new PngEncoderOptions(this));
await encoder.EncodeAsync(image, stream, cancellationToken).ConfigureAwait(false);
}
}
}

2
tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs

@ -7,7 +7,7 @@ using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using ImageMagick; using ImageMagick;
using ImageMagick.Formats.Bmp; using ImageMagick.Formats;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;

4
tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs

@ -57,10 +57,10 @@ namespace SixLabors.ImageSharp.Tests
new GifConfigurationModule(), new GifConfigurationModule(),
new TgaConfigurationModule()); new TgaConfigurationModule());
// Magick codecs should work on all platforms IImageEncoder pngEncoder = IsWindows ? (IImageEncoder)SystemDrawingReferenceEncoder.Png : new ImageSharpPngEncoderWithDefaultConfiguration();
IImageEncoder pngEncoder = IsWindows ? (IImageEncoder)SystemDrawingReferenceEncoder.Png : new PngEncoder();
IImageEncoder bmpEncoder = IsWindows ? (IImageEncoder)SystemDrawingReferenceEncoder.Bmp : new BmpEncoder(); IImageEncoder bmpEncoder = IsWindows ? (IImageEncoder)SystemDrawingReferenceEncoder.Bmp : new BmpEncoder();
// Magick codecs should work on all platforms
cfg.ConfigureCodecs( cfg.ConfigureCodecs(
PngFormat.Instance, PngFormat.Instance,
MagickReferenceDecoder.Instance, MagickReferenceDecoder.Instance,

21
tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs

@ -24,8 +24,6 @@ namespace SixLabors.ImageSharp.Tests
private static readonly Lazy<string> SolutionDirectoryFullPathLazy = new Lazy<string>(GetSolutionDirectoryFullPathImpl); private static readonly Lazy<string> SolutionDirectoryFullPathLazy = new Lazy<string>(GetSolutionDirectoryFullPathImpl);
private static readonly Lazy<bool> RunsOnCiLazy = new Lazy<bool>(() => bool.TryParse(Environment.GetEnvironmentVariable("CI"), out bool isCi) && isCi);
private static readonly Lazy<string> NetCoreVersionLazy = new Lazy<string>(GetNetCoreVersion); private static readonly Lazy<string> NetCoreVersionLazy = new Lazy<string>(GetNetCoreVersion);
static TestEnvironment() => PrepareRemoteExecutor(); static TestEnvironment() => PrepareRemoteExecutor();
@ -40,7 +38,20 @@ namespace SixLabors.ImageSharp.Tests
/// <summary> /// <summary>
/// Gets a value indicating whether test execution runs on CI. /// Gets a value indicating whether test execution runs on CI.
/// </summary> /// </summary>
internal static bool RunsOnCI => RunsOnCiLazy.Value; #if ENV_CI
internal static bool RunsOnCI => true;
#else
internal static bool RunsOnCI => false;
#endif
/// <summary>
/// Gets a value indicating whether test execution is running with code coverage testing enabled.
/// </summary>
#if ENV_CODECOV
internal static bool RunsWithCodeCoverage => true;
#else
internal static bool RunsWithCodeCoverage => false;
#endif
internal static string SolutionDirectoryFullPath => SolutionDirectoryFullPathLazy.Value; internal static string SolutionDirectoryFullPath => SolutionDirectoryFullPathLazy.Value;
@ -59,14 +70,14 @@ namespace SixLabors.ImageSharp.Tests
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new Exception( throw new DirectoryNotFoundException(
$"Unable to find ImageSharp solution directory from {TestAssemblyFile} because of {ex.GetType().Name}!", $"Unable to find ImageSharp solution directory from {TestAssemblyFile} because of {ex.GetType().Name}!",
ex); ex);
} }
if (directory == null) if (directory == null)
{ {
throw new Exception($"Unable to find ImageSharp solution directory from {TestAssemblyFile}!"); throw new DirectoryNotFoundException($"Unable to find ImageSharp solution directory from {TestAssemblyFile}!");
} }
} }

61
tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

@ -34,18 +34,16 @@ namespace SixLabors.ImageSharp.Tests
bool appendPixelTypeToFileName = true, bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true, bool appendSourceFileOrDescription = true,
IImageEncoder encoder = null) IImageEncoder encoder = null)
{ => image.DebugSave(
image.DebugSave(
provider, provider,
(object)testOutputDetails, (object)testOutputDetails,
extension, extension,
appendPixelTypeToFileName, appendPixelTypeToFileName,
appendSourceFileOrDescription, appendSourceFileOrDescription,
encoder); encoder);
}
/// <summary> /// <summary>
/// Saves the image only when not running in the CI server. /// Saves the image for debugging purpose.
/// </summary> /// </summary>
/// <param name="image">The image.</param> /// <param name="image">The image.</param>
/// <param name="provider">The image provider.</param> /// <param name="provider">The image provider.</param>
@ -64,12 +62,11 @@ namespace SixLabors.ImageSharp.Tests
bool appendSourceFileOrDescription = true, bool appendSourceFileOrDescription = true,
IImageEncoder encoder = null) IImageEncoder encoder = null)
{ {
if (TestEnvironment.RunsOnCI) if (TestEnvironment.RunsWithCodeCoverage)
{ {
return image; return image;
} }
// We are running locally then we want to save it out
provider.Utility.SaveTestOutputFile( provider.Utility.SaveTestOutputFile(
image, image,
extension, extension,
@ -86,12 +83,10 @@ namespace SixLabors.ImageSharp.Tests
IImageEncoder encoder, IImageEncoder encoder,
FormattableString testOutputDetails, FormattableString testOutputDetails,
bool appendPixelTypeToFileName = true) bool appendPixelTypeToFileName = true)
{ => image.DebugSave(provider, encoder, (object)testOutputDetails, appendPixelTypeToFileName);
image.DebugSave(provider, encoder, (object)testOutputDetails, appendPixelTypeToFileName);
}
/// <summary> /// <summary>
/// Saves the image only when not running in the CI server. /// Saves the image for debugging purpose.
/// </summary> /// </summary>
/// <param name="image">The image</param> /// <param name="image">The image</param>
/// <param name="provider">The image provider</param> /// <param name="provider">The image provider</param>
@ -104,19 +99,11 @@ namespace SixLabors.ImageSharp.Tests
IImageEncoder encoder, IImageEncoder encoder,
object testOutputDetails = null, object testOutputDetails = null,
bool appendPixelTypeToFileName = true) bool appendPixelTypeToFileName = true)
{ => provider.Utility.SaveTestOutputFile(
if (TestEnvironment.RunsOnCI)
{
return;
}
// We are running locally then we want to save it out
provider.Utility.SaveTestOutputFile(
image, image,
encoder: encoder, encoder: encoder,
testOutputDetails: testOutputDetails, testOutputDetails: testOutputDetails,
appendPixelTypeToFileName: appendPixelTypeToFileName); appendPixelTypeToFileName: appendPixelTypeToFileName);
}
public static Image<TPixel> DebugSaveMultiFrame<TPixel>( public static Image<TPixel> DebugSaveMultiFrame<TPixel>(
this Image<TPixel> image, this Image<TPixel> image,
@ -126,17 +113,17 @@ namespace SixLabors.ImageSharp.Tests
bool appendPixelTypeToFileName = true) bool appendPixelTypeToFileName = true)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
if (TestEnvironment.RunsOnCI) if (TestEnvironment.RunsWithCodeCoverage)
{ {
return image; return image;
} }
// We are running locally then we want to save it out
provider.Utility.SaveTestOutputFileMultiFrame( provider.Utility.SaveTestOutputFileMultiFrame(
image, image,
extension, extension,
testOutputDetails: testOutputDetails, testOutputDetails: testOutputDetails,
appendPixelTypeToFileName: appendPixelTypeToFileName); appendPixelTypeToFileName: appendPixelTypeToFileName);
return image; return image;
} }
@ -149,15 +136,13 @@ namespace SixLabors.ImageSharp.Tests
bool appendPixelTypeToFileName = true, bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true) bool appendSourceFileOrDescription = true)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => image.CompareToReferenceOutput(
return image.CompareToReferenceOutput(
provider, provider,
(object)testOutputDetails, (object)testOutputDetails,
extension, extension,
grayscale, grayscale,
appendPixelTypeToFileName, appendPixelTypeToFileName,
appendSourceFileOrDescription); appendSourceFileOrDescription);
}
/// <summary> /// <summary>
/// Compares the image against the expected Reference output, throws an exception if the images are not similar enough. /// Compares the image against the expected Reference output, throws an exception if the images are not similar enough.
@ -181,8 +166,7 @@ namespace SixLabors.ImageSharp.Tests
bool appendPixelTypeToFileName = true, bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true) bool appendSourceFileOrDescription = true)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => CompareToReferenceOutput(
return CompareToReferenceOutput(
image, image,
ImageComparer.Tolerant(), ImageComparer.Tolerant(),
provider, provider,
@ -191,7 +175,6 @@ namespace SixLabors.ImageSharp.Tests
grayscale, grayscale,
appendPixelTypeToFileName, appendPixelTypeToFileName,
appendSourceFileOrDescription); appendSourceFileOrDescription);
}
public static Image<TPixel> CompareToReferenceOutput<TPixel>( public static Image<TPixel> CompareToReferenceOutput<TPixel>(
this Image<TPixel> image, this Image<TPixel> image,
@ -202,15 +185,13 @@ namespace SixLabors.ImageSharp.Tests
bool grayscale = false, bool grayscale = false,
bool appendPixelTypeToFileName = true) bool appendPixelTypeToFileName = true)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => image.CompareToReferenceOutput(
return image.CompareToReferenceOutput(
comparer, comparer,
provider, provider,
(object)testOutputDetails, (object)testOutputDetails,
extension, extension,
grayscale, grayscale,
appendPixelTypeToFileName); appendPixelTypeToFileName);
}
/// <summary> /// <summary>
/// Compares the image against the expected Reference output, throws an exception if the images are not similar enough. /// Compares the image against the expected Reference output, throws an exception if the images are not similar enough.
@ -263,8 +244,7 @@ namespace SixLabors.ImageSharp.Tests
bool appendPixelTypeToFileName = true, bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true) bool appendSourceFileOrDescription = true)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => image.CompareFirstFrameToReferenceOutput(
return image.CompareFirstFrameToReferenceOutput(
comparer, comparer,
provider, provider,
(object)testOutputDetails, (object)testOutputDetails,
@ -272,7 +252,6 @@ namespace SixLabors.ImageSharp.Tests
grayscale, grayscale,
appendPixelTypeToFileName, appendPixelTypeToFileName,
appendSourceFileOrDescription); appendSourceFileOrDescription);
}
public static Image<TPixel> CompareFirstFrameToReferenceOutput<TPixel>( public static Image<TPixel> CompareFirstFrameToReferenceOutput<TPixel>(
this Image<TPixel> image, this Image<TPixel> image,
@ -508,9 +487,7 @@ namespace SixLabors.ImageSharp.Tests
ITestImageProvider provider, ITestImageProvider provider,
IImageDecoder referenceDecoder = null) IImageDecoder referenceDecoder = null)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => CompareToOriginal(image, provider, ImageComparer.Tolerant(), referenceDecoder);
return CompareToOriginal(image, provider, ImageComparer.Tolerant(), referenceDecoder);
}
public static Image<TPixel> CompareToOriginal<TPixel>( public static Image<TPixel> CompareToOriginal<TPixel>(
this Image<TPixel> image, this Image<TPixel> image,
@ -584,14 +561,12 @@ namespace SixLabors.ImageSharp.Tests
bool appendPixelTypeToFileName = true, bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true) bool appendSourceFileOrDescription = true)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => provider.VerifyOperation(
provider.VerifyOperation(
ImageComparer.Tolerant(), ImageComparer.Tolerant(),
operation, operation,
testOutputDetails, testOutputDetails,
appendPixelTypeToFileName, appendPixelTypeToFileName,
appendSourceFileOrDescription); appendSourceFileOrDescription);
}
/// <summary> /// <summary>
/// Utility method for doing the following in one step: /// Utility method for doing the following in one step:
@ -606,14 +581,12 @@ namespace SixLabors.ImageSharp.Tests
bool appendPixelTypeToFileName = true, bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true) bool appendSourceFileOrDescription = true)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => provider.VerifyOperation(
provider.VerifyOperation(
comparer, comparer,
operation, operation,
$"", $"",
appendPixelTypeToFileName, appendPixelTypeToFileName,
appendSourceFileOrDescription); appendSourceFileOrDescription);
}
/// <summary> /// <summary>
/// Utility method for doing the following in one step: /// Utility method for doing the following in one step:
@ -627,9 +600,7 @@ namespace SixLabors.ImageSharp.Tests
bool appendPixelTypeToFileName = true, bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true) bool appendSourceFileOrDescription = true)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ => provider.VerifyOperation(operation, $"", appendPixelTypeToFileName, appendSourceFileOrDescription);
provider.VerifyOperation(operation, $"", appendPixelTypeToFileName, appendSourceFileOrDescription);
}
/// <summary> /// <summary>
/// Loads the expected image with a reference decoder + compares it to <paramref name="image"/>. /// Loads the expected image with a reference decoder + compares it to <paramref name="image"/>.

2
tests/ImageSharp.Tests/TestUtilities/TestUtils.cs

@ -240,7 +240,7 @@ namespace SixLabors.ImageSharp.Tests
using (Image<TPixel> image = provider.GetImage()) using (Image<TPixel> image = provider.GetImage())
{ {
FormattableString testOutputDetails = $""; FormattableString testOutputDetails = $"";
image.Mutate(ctx => { testOutputDetails = processAndGetTestOutputDetails(ctx); }); image.Mutate(ctx => testOutputDetails = processAndGetTestOutputDetails(ctx));
image.DebugSave( image.DebugSave(
provider, provider,

3
tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs

@ -8,7 +8,6 @@ using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
using Xunit; using Xunit;
@ -85,7 +84,7 @@ namespace SixLabors.ImageSharp.Tests
} }
[Theory] [Theory]
[InlineData("lol/foo.png", typeof(PngEncoder))] [InlineData("lol/foo.png", typeof(ImageSharpPngEncoderWithDefaultConfiguration))]
[InlineData("lol/Rofl.bmp", typeof(BmpEncoder))] [InlineData("lol/Rofl.bmp", typeof(BmpEncoder))]
[InlineData("lol/Baz.JPG", typeof(JpegEncoder))] [InlineData("lol/Baz.JPG", typeof(JpegEncoder))]
[InlineData("lol/Baz.gif", typeof(GifEncoder))] [InlineData("lol/Baz.gif", typeof(GifEncoder))]

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(-20,-10).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:57376aa4446fc2d034d2a0cb627163ac416d1b6768b063b2c11ccf8517443bda oid sha256:2ef489dc0837b382ad7c7ead6b7c7042dfbfba39902d4cc81b5f3805d5b03967
size 10135 size 9175

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50__original.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:9c83b59471a50df9e1d7b8f0e35c50aa417cd3be1730d6369f47f5cc99b87cef oid sha256:99d6c1d6b092a2feba2aebe2e09c521c3cc9682f3d748927cdc3cbaa38448b28
size 6405 size 710

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:f9f149093c5d9d487f3e16cb59e6807ea6ce581b5c307e57aeb4edaf921de9ca oid sha256:04a3b84c668758b586f4d998f080ef96b09f726b0298b28f5dd3d739b0e90744
size 15138 size 13138

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:f9f149093c5d9d487f3e16cb59e6807ea6ce581b5c307e57aeb4edaf921de9ca oid sha256:04a3b84c668758b586f4d998f080ef96b09f726b0298b28f5dd3d739b0e90744
size 15138 size 13138

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:0c7467702a784807a3a5813a75d5f643655ed5ad427e978d6ee079da67b05961 oid sha256:6ea7ca66c31474c0bb9673a0d85c1c7465e387ebabf4e2d1e8f9daebfc7c8f34
size 15363 size 13956

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:a836c63c0912f69683bc9c8f59687b11e6b84f257816a01ff979ad0b6f4ab656 oid sha256:6dd98ac441f3c20ea999f058c7b21601d5981d46e9b77709c25f2930a64edb93
size 19059 size 17148

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:05de30dc282a0b8a096a476f689a1d2b6bb298098692a2f665c59c3d14902aa6 oid sha256:d5c4772d9b9dc57c4b5d47450ec9d02d96e40656cf2015f171b5425945f8a598
size 20426 size 18726

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:8892179d8edf96583900048bdd895eff24e57be0105e91efafe1de971414db0e oid sha256:bb025c4470cec1b0d6544924e46b84cbdb90d75da5d0f879f2c7d7ec9875dee2
size 22457 size 20574

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:2f351ec6ae6df56537e383494984c2fbcf35a66c44a6ce53d6fd8d6d74a330f3 oid sha256:c4abaa06827cb779026f8fbb655692bdd8adab37ae5b00c3ae18ebea456eb8d9
size 15342 size 13459

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:b0fb38dbdded32a1518d62ac79f4aa88133aaddad947f23c1066dc33d6938e0b oid sha256:3435ade8f7988779280820342e16881b049f717735d2218ac5a971a1bd807db1
size 15372 size 13448

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:0be42a5fd4ff10680af74a99ed1e0561ae38181f4efe4641bd63891222dcdf3c oid sha256:f0098aa45e820544dd16a58396fa70860886b7d79900916ed97376a8328a5ff2
size 15283 size 13367

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:1eabaf35e0dda3eccd5e8866ddd65e0c45557c8d5cc29423a99e2f377ee1bfa9 oid sha256:7729495277b80a42e24dd8f40cdd8a280443575710fb4e199e4871c28b558271
size 16271 size 14253

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:5ab9bea8f45e7d29d7a8c5e3d0182c0f3f3aa7014aa358883dee53db6dfeb3f7 oid sha256:a2304c234b93bdabaa018263dec70e62090ad1bbb7005ef62643b88600a863fb
size 14076 size 12157

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ea452bc46c508f6990870a34d908224a58aa350c4ccebabd4fa6ba138e8034a0 oid sha256:54b0da9646b7f4cf83df784d69dfbec48e0bdc1788d70a9872817543f72f57c1
size 18383 size 16829

4
tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:4ce9f105031562af60baff3619b9bc84a9f76d77a555a334149d13ddf1e9b44b oid sha256:d0c8ccdfbf6b1c961f6531ae61207a7f89507f469c875677f1755ea3d6c8d900
size 269077 size 326504

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:56b0fc0343c220f672611d1adceffd4f59ba917e7c5766b19568271d0b4a95e0 oid sha256:14231fa7c5c98504277b6452901679027661c5e272106bdcfc516dd519a5ff6c
size 788 size 1049

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:56b0fc0343c220f672611d1adceffd4f59ba917e7c5766b19568271d0b4a95e0 oid sha256:14231fa7c5c98504277b6452901679027661c5e272106bdcfc516dd519a5ff6c
size 788 size 1049

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:56b0fc0343c220f672611d1adceffd4f59ba917e7c5766b19568271d0b4a95e0 oid sha256:d55bf31ae306fcf91993b488444e83ad0f684f4a2642879e38e27e7b9fb1fa56
size 788 size 1051

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:56b0fc0343c220f672611d1adceffd4f59ba917e7c5766b19568271d0b4a95e0 oid sha256:14231fa7c5c98504277b6452901679027661c5e272106bdcfc516dd519a5ff6c
size 788 size 1049

3
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer16x16.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:208a0b9a189c8801e97495a93302814679441bbbe1769810eb37bcb52a78518f
size 83344

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer2x2.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:97f4582029805abbae7575df9935a77b55ab7c4f4190f7cfb7a68b3f3dac9cfc oid sha256:c95ae441b8b090a0c838db5ed3e9b3ae1040225420e79b76c806f88b96716b8f
size 39840 size 80344

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer4x4.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:186be7a7c23b9150e669b3aa15e3f7af7e619759ecc4ea2a7be7645c124caebb oid sha256:e5ab9eb0b80de50f117446c46025918893c431c228e212bef9371f4f788cee14
size 40570 size 82652

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Bayer8x8.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e038cf3f08e27f7b83921256112378f69cc56880added2fc60bf1f710e2e497e oid sha256:f76c909b7e804c8dd80b07fd5346d2036d2fded2bf9a855bd20f7da154a111f3
size 40547 size 83554

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_Bike_Ordered3x3.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:f071a490e7a297e7f861a9669afc7641f2fcb264cdef84f562d41fd97ed66504 oid sha256:656dfb6c9a53830d915a8c8810d09872333a9230073e25b4f0668269afb15e00
size 40751 size 83188

3
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer16x16.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b56aa9a03e7f6733fac6b6ceddba50e85727201c4f79aea64540cc79f7fd942e
size 88333

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer2x2.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:9994ba45741132a51fab9fdd71e6aaab7ce748c6c393b7515b85bad71878e3ee oid sha256:863debcf1bc4a4e3fb0e3c29b8b3f8b98bb7ac47901e89a90a57a2dde5d81f53
size 49984 size 90431

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer4x4.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:5360890509baffe70fd84499a0b0e18057f1220660782b1d82e39d36c04dc3f1 oid sha256:a92785de634c09d73dc91d1a33e52dedd7d5dea79d269753d959f2a1f81afb2b
size 49638 size 89207

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Bayer8x8.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:1e8603ec1daebec049e34658a07bb85333ac996d5d16d64aa318b7f47d966bf6 oid sha256:e0aeb15a04553142cade051d523bbc18b2e63997efa0b0c5f5b8bab8662074f7
size 49253 size 88550

4
tests/Images/External/ReferenceOutput/DitherTests/DitherFilter_WorksWithAllDitherers_CalliphoraPartial_Ordered3x3.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:74bdc85112c696133c251d05080c100675cb72f07ed8a8e542faa5255fd0c942 oid sha256:4801d48fc6691bc2fd555a4bed8a7abdde7edac3dc13b33da580688d11bc4eb4
size 49248 size 89543

4
tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawTransformed.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:2c83df7a2f70aec8f150799055ce42db09568b47b95216c91a79233ce69381d5 oid sha256:233d8c6c9e101dddf5d210d47c7a20807f8f956738289068ea03b774258ef8c6
size 191563 size 182754

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:da1630504bf471ef7ee717dd575c9bfaa3cae3839ad9415a5f1abf2c5fa7d418 oid sha256:adc156f6010679f2ff076405557d0a34cd50464240bbafafbf44edf37b5a1186
size 319991 size 321968

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:8b2c154c970f5a80ff3e97f0c0fbe832998e72687be090073416a96d41647141 oid sha256:ec93dd8fc45e9eb3b1ad13bd89dfc487f5d6eccd2ad8fa1fede67fa7819a263a
size 296210 size 299393

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:2e2747cbfc098f4368fd87599b06debbe2a21bcf29260788b868bf9333c9f03c oid sha256:8f3ef9dab0169bd262408a30ce2a1d20da5acb331fd56ce66de2f7efe4555a9a
size 296458 size 299734

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_OrderedDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:9adda119665eb27ca1475ce19ab0958b00d6778b9e1733c42cf5e77350f40000 oid sha256:f3d8d9e978668ae8f76004dc2a8440ffe2f55875ee92046ca2be02f426def1a6
size 332734 size 333260

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:b7295356c1e0c5f59220bfaf154760e86663709c8e0dbaf156efa13ac45b947a oid sha256:bdb6866053be7dbe1e56e6972b50bc030d30a050f73a4429993e3c639e06d345
size 348440 size 349125

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:07b4f6147884f323c99734ddf295d2fc1a072ac95f003d63e217aaa63cdb6b7a oid sha256:0adaaae399376c94af866adfcb2c5777c0dd91d2d4424f24490909e68d2483c9
size 325067 size 326321

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e7a849cce2215bc40da6fd3581e8a7fc87b6ef916e227c622c901985d6ee9b1b oid sha256:0571bde66f19b41cf1ba6f3b63f3d380a1025ae2f92dda8b9c494f8869c325e4
size 333793 size 334758

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:a07a6698cca2e13b674102c92f23b934955406c61c485514d9bbbe62da3e04f5 oid sha256:8cc6c263430489a8866fab47c26f399a034b0dd583d27b12edc68244919321d0
size 353191 size 353592

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:2fc87ffa7c22b4b054903c6f19bdfcca1d0cdb01df6f853ae4361da3c99f5ab6 oid sha256:ec01d4ee9173d01f92b5643782f4b6c7e0b4342b530acf6062f5f17c6d7b1e9a
size 34595 size 36290

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:243d875938b1a68e6c49a07259a9a155f0d36a2fbdc3350b38e13a3d0c6bff7f oid sha256:2ed04ff17bc4d7c57a9594bb4872f430cc3df4d92c7199d5c5db2420ecc20a95
size 36085 size 38303

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:8076572e9e9a391939c166491553421a83aec5085b1bab155fdf7459840054ed oid sha256:2f8e53d995f27780851c044d552473ee52ec9dcc2e0dfa9a806c9f8d2fd62692
size 37194 size 39251

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:7ad464bf9a0dfbebbc7f3b7a3ff641e2db103456943a8fde5d71d675df66f4c6 oid sha256:6a7a1cefa7e70387ccb9e90c5633725ce936635da39c131a59cec7089392c358
size 37895 size 39744

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.25.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:4b7369ddaf16e3e13554bf7647096e8947f7ccadadc58646b7c53beee6304f90 oid sha256:a3b56c451b5e7461782dec2f5dccab18e7ad33efe3d9f1906421c32c75923648
size 17047 size 17790

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.5.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:b6e0c73170db2f2d84d4951108b8cdd4967e4272ecadcf5268cd7b34d81df760 oid sha256:c4d81ab162bd065f438504ea2a44be93cefd7f1b31d7d983e23108e8e19b86fa
size 17786 size 18390

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_0.75.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:d54587aab60d4200bb6e01a0ff707230959602b93ff55fbf48c85b61cfb0932a oid sha256:1d2cb1111d2a3915072ca53404215052bbff42ff9639e8e3c2b4f6a70591fd0e
size 18161 size 19145

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WebSafePaletteQuantizer_OrderedDither_1.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:fee85efaf11a2da44933c5ab9e5858558044d1707e40a6fadda6ed5214e40c9c oid sha256:1133884d19f663d3c643ebe11bdeac65e2ab3d533be43a40b61b3292ea59cd3b
size 18453 size 19680

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.25.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:4947713871d8e832f74f855bc60c3efdacef46fa826a37dfa4ceb2a1e4e23680 oid sha256:64b29bbd6edca8e444822a97ce9bc674db175c299cbec1cbe596552419f49be7
size 21484 size 22239

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.5.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:61b794eb8ff00a1088f4584c98066fd5e309c1711f2a430ae24fc482524e6573 oid sha256:ae09ad6a81dbfc56c60b7e47720338b3ba3b8aa29982016c36a39baa33f75054
size 22374 size 23353

4
tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_0.75.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:fe1e3eae55fad479016d869a3b483155f3be947636da96d105e16072fbb4e350 oid sha256:e9e9094177282dd635a02b97855299e9275af364fd66812dd72b3ef2545b5660
size 22968 size 24487

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save