Browse Source

Merge branch 'SixLabors:master' into master

pull/1835/head
Ynse Hoornenborg 5 years ago
committed by GitHub
parent
commit
319beee005
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. 141
      .github/workflows/build-and-test.yml
  5. 5
      .gitignore
  6. 13
      Directory.Build.props
  7. 225
      ImageSharp.sln
  8. 47
      README.md
  9. 11
      codecov.yml
  10. 2
      shared-infrastructure
  11. 12
      src/ImageSharp/Advanced/AotCompilerTools.cs
  12. 1
      src/ImageSharp/Advanced/ParallelExecutionSettings.cs
  13. 151
      src/ImageSharp/Color/Color.Conversions.cs
  14. 106
      src/ImageSharp/Color/Color.cs
  15. 2
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs
  16. 23
      src/ImageSharp/Common/ByteOrder.cs
  17. 7
      src/ImageSharp/Common/Extensions/StreamExtensions.cs
  18. 4
      src/ImageSharp/Common/Helpers/DebugGuard.cs
  19. 21
      src/ImageSharp/Common/Helpers/ExifResolutionValues.cs
  20. 4
      src/ImageSharp/Common/Helpers/InliningOptions.cs
  21. 139
      src/ImageSharp/Common/Helpers/Numerics.cs
  22. 32
      src/ImageSharp/Common/Helpers/RuntimeEnvironment.cs
  23. 170
      src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs
  24. 2
      src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs
  25. 45
      src/ImageSharp/Common/Helpers/UnitConverter.cs
  26. 172
      src/ImageSharp/Compression/Zlib/Adler32.cs
  27. 2
      src/ImageSharp/Compression/Zlib/Crc32.Lut.cs
  28. 217
      src/ImageSharp/Compression/Zlib/Crc32.cs
  29. 81
      src/ImageSharp/Compression/Zlib/DeflateCompressionLevel.cs
  30. 2
      src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs
  31. 4
      src/ImageSharp/Compression/Zlib/Deflater.cs
  32. 2
      src/ImageSharp/Compression/Zlib/DeflaterConstants.cs
  33. 43
      src/ImageSharp/Compression/Zlib/DeflaterEngine.cs
  34. 30
      src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs
  35. 29
      src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs
  36. 33
      src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs
  37. 0
      src/ImageSharp/Compression/Zlib/README.md
  38. 15
      src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs
  39. 2
      src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs
  40. 0
      src/ImageSharp/Compression/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf
  41. 44
      src/ImageSharp/Configuration.cs
  42. 12
      src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
  43. 320
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  44. 2
      src/ImageSharp/Formats/Bmp/BmpEncoder.cs
  45. 273
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  46. 4
      src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
  47. 31
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  48. 28
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  49. 1
      src/ImageSharp/Formats/Gif/LzwEncoder.cs
  50. 210
      src/ImageSharp/Formats/ImageExtensions.Save.cs
  51. 4
      src/ImageSharp/Formats/ImageExtensions.Save.tt
  52. 193
      src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs
  53. 149
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Intrinsic.cs
  54. 2
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs
  55. 560
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  56. 47
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx2.cs
  57. 45
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykBasic.cs
  58. 34
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs
  59. 32
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx2.cs
  60. 38
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleBasic.cs
  61. 40
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs
  62. 33
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs
  63. 40
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs
  64. 50
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx2.cs
  65. 37
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs
  66. 52
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector4.cs
  67. 47
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector8.cs
  68. 64
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx2.cs
  69. 43
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKBasic.cs
  70. 60
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector8.cs
  71. 15
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs
  72. 156
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
  73. 6
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs
  74. 231
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
  75. 17
      src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs
  76. 32
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs
  77. 33
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs
  78. 64
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
  79. 91
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
  80. 181
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs
  81. 144
      src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs
  82. 44
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs
  83. 172
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
  84. 29
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs
  85. 689
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
  86. 12
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs
  87. 2
      src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs
  88. 8
      src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs
  89. 114
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs
  90. 148
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs
  91. 195
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs
  92. 121
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs
  93. 122
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs
  94. 90
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
  95. 161
      src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.Intrinsic.cs
  96. 486
      src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs
  97. 199
      src/ImageSharp/Formats/Jpeg/Components/Quantization.cs
  98. 300
      src/ImageSharp/Formats/Jpeg/Components/ZigZag.Intrinsic.cs
  99. 79
      src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs
  100. 14
      src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs

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.category-style.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

141
.github/workflows/build-and-test.yml

@ -1,19 +1,37 @@
name: Build name: Build
on: on:
push: push:
branches: branches:
- master - master
tags: tags:
- "v*" - "v*"
pull_request: pull_request:
branches: branches:
- master - master
jobs: jobs:
Build: Build:
strategy: strategy:
matrix: matrix:
options: options:
- os: ubuntu-latest
framework: net6.0
sdk: 6.0.x
sdk-preview: true
runtime: -x64
codecov: false
- os: macos-latest
framework: net6.0
sdk: 6.0.x
sdk-preview: true
runtime: -x64
codecov: false
- os: windows-latest
framework: net6.0
sdk: 6.0.x
sdk-preview: true
runtime: -x64
codecov: false
- os: ubuntu-latest - os: ubuntu-latest
framework: net5.0 framework: net5.0
runtime: -x64 runtime: -x64
@ -52,37 +70,38 @@ jobs:
codecov: false codecov: false
runs-on: ${{matrix.options.os}} runs-on: ${{matrix.options.os}}
if: "!contains(github.event.head_commit.message, '[skip ci]')"
steps: steps:
- uses: actions/checkout@v2 - name: Git Config
shell: bash
run: |
git config --global core.autocrlf false
git config --global core.longpaths true
- name: Git Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: recursive
# See https://github.com/actions/checkout/issues/165#issuecomment-657673315 # See https://github.com/actions/checkout/issues/165#issuecomment-657673315
- name: Create LFS file list - name: Git Create LFS FileList
run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id
- name: Restore LFS cache - name: Git Setup LFS Cache
uses: actions/cache@v2 uses: actions/cache@v2
id: lfs-cache id: lfs-cache
with: with:
path: .git/lfs path: .git/lfs
key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1 key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1
- name: Git LFS Pull - name: Git Pull LFS
run: git lfs pull run: git lfs pull
- name: Install NuGet - name: NuGet Install
uses: NuGet/setup-nuget@v1 uses: NuGet/setup-nuget@v1
- name: Setup Git - name: NuGet Setup Cache
shell: bash
run: |
git config --global core.autocrlf false
git config --global core.longpaths true
git fetch --prune --unshallow
git submodule -q update --init --recursive
- name: Setup NuGet Cache
uses: actions/cache@v2 uses: actions/cache@v2
id: nuget-cache id: nuget-cache
with: with:
@ -90,60 +109,94 @@ jobs:
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }}
restore-keys: ${{ runner.os }}-nuget- restore-keys: ${{ runner.os }}-nuget-
- name: Build - name: DotNet Setup Preview
if: ${{ matrix.options.sdk-preview == true }}
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ matrix.options.sdk }}
include-prerelease: true
- name: DotNet Build
if: ${{ matrix.options.sdk-preview != true }}
shell: pwsh shell: pwsh
run: ./ci-build.ps1 "${{matrix.options.framework}}" run: ./ci-build.ps1 "${{matrix.options.framework}}"
env: env:
SIXLABORS_TESTING: True SIXLABORS_TESTING: True
- name: Test - name: DotNet Build Preview
if: ${{ matrix.options.sdk-preview == true }}
shell: pwsh
run: ./ci-build.ps1 "${{matrix.options.framework}}"
env:
SIXLABORS_TESTING_PREVIEW: True
- name: DotNet Test
if: ${{ matrix.options.sdk-preview != true }}
shell: pwsh shell: pwsh
run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}"
env: env:
SIXLABORS_TESTING: True SIXLABORS_TESTING: True
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
- name: DotNet Test Preview
if: ${{ matrix.options.sdk-preview == true }}
shell: pwsh
run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}"
env:
SIXLABORS_TESTING_PREVIEW: True
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
- name: Export Failed Output - name: Export Failed Output
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
if: failure() if: failure()
with: with:
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip
path: tests/Images/ActualOutput/ path: tests/Images/ActualOutput/
- name: Update Codecov - name: Codecov Update
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v1
if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors') if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors')
with: with:
flags: unittests flags: unittests
Publish: Publish:
needs: [Build] needs: [Build]
runs-on: windows-latest runs-on: ubuntu-latest
if: (github.event_name == 'push') if: (github.event_name == 'push')
steps: steps:
- uses: actions/checkout@v2 - name: Git Config
- name: Install NuGet
uses: NuGet/setup-nuget@v1
- name: Setup Git
shell: bash shell: bash
run: | run: |
git config --global core.autocrlf false git config --global core.autocrlf false
git config --global core.longpaths true git config --global core.longpaths true
git fetch --prune --unshallow
git submodule -q update --init --recursive
- name: Pack - name: Git Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: recursive
- name: NuGet Install
uses: NuGet/setup-nuget@v1
- name: NuGet Setup Cache
uses: actions/cache@v2
id: nuget-cache
with:
path: ~/.nuget
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }}
restore-keys: ${{ runner.os }}-nuget-
- name: DotNet Pack
shell: pwsh shell: pwsh
run: ./ci-pack.ps1 run: ./ci-pack.ps1
- name: Publish to MyGet - name: MyGet Publish
shell: pwsh shell: pwsh
run: | run: |
nuget.exe push .\artifacts\*.nupkg ${{secrets.MYGET_TOKEN}} -Source https://www.myget.org/F/sixlabors/api/v2/package dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v2/package
nuget.exe push .\artifacts\*.snupkg ${{secrets.MYGET_TOKEN}} -Source https://www.myget.org/F/sixlabors/api/v3/index.json dotnet nuget push .\artifacts\*.snupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v3/index.json
# TODO: If github.ref starts with 'refs/tags' then it was tag push and we can optionally push out package to nuget.org # TODO: If github.ref starts with 'refs/tags' then it was tag push and we can optionally push out package to nuget.org

5
.gitignore

@ -221,4 +221,9 @@ artifacts/
# Tests # Tests
**/Images/ActualOutput **/Images/ActualOutput
**/Images/ReferenceOutput **/Images/ReferenceOutput
**/Images/Input/MemoryStress
.DS_Store .DS_Store
#lfs
hooks/**
lfs/**

13
Directory.Build.props

@ -18,4 +18,17 @@
<!-- 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" />
<PropertyGroup Condition="$(SIXLABORS_TESTING_PREVIEW) == true">
<!-- Workaround various issues bound to implicit language features. -->
<LangVersion>preview</LangVersion>
</PropertyGroup>
<!--
Ensure all custom build configurations based upon "Release" are optimized.
This is easier than setting each project individually.
-->
<PropertyGroup Condition="$(Configuration.StartsWith('Release')) == true">
<Optimize>true</Optimize>
</PropertyGroup>
</Project> </Project>

225
ImageSharp.sln

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 17
VisualStudioVersion = 16.0.28902.138 VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_root", "_root", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_root", "_root", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
@ -13,6 +13,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_root", "_root", "{C317F1B1
ci-build.ps1 = ci-build.ps1 ci-build.ps1 = ci-build.ps1
ci-pack.ps1 = ci-pack.ps1 ci-pack.ps1 = ci-pack.ps1
ci-test.ps1 = ci-test.ps1 ci-test.ps1 = ci-test.ps1
codecov.yml = codecov.yml
Directory.Build.props = Directory.Build.props Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets Directory.Build.targets = Directory.Build.targets
LICENSE = LICENSE LICENSE = LICENSE
@ -379,6 +380,170 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Png", "Png", "{E1C42A6F-913
tests\Images\Input\Png\zlib-ztxt-bad-header.png = tests\Images\Input\Png\zlib-ztxt-bad-header.png tests\Images\Input\Png\zlib-ztxt-bad-header.png = tests\Images\Input\Png\zlib-ztxt-bad-header.png
EndProjectSection EndProjectSection
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webp", "Webp", "{983A31E2-5E26-4058-BD6E-03B4922D4BBF}"
ProjectSection(SolutionItems) = preProject
tests\Images\Input\Webp\1602311202.webp = tests\Images\Input\Webp\1602311202.webp
tests\Images\Input\Webp\alpha_color_cache.webp = tests\Images\Input\Webp\alpha_color_cache.webp
tests\Images\Input\Webp\alpha_filter_0_method_0.webp = tests\Images\Input\Webp\alpha_filter_0_method_0.webp
tests\Images\Input\Webp\alpha_filter_0_method_1.webp = tests\Images\Input\Webp\alpha_filter_0_method_1.webp
tests\Images\Input\Webp\alpha_filter_1.webp = tests\Images\Input\Webp\alpha_filter_1.webp
tests\Images\Input\Webp\alpha_filter_1_method_0.webp = tests\Images\Input\Webp\alpha_filter_1_method_0.webp
tests\Images\Input\Webp\alpha_filter_1_method_1.webp = tests\Images\Input\Webp\alpha_filter_1_method_1.webp
tests\Images\Input\Webp\alpha_filter_2.webp = tests\Images\Input\Webp\alpha_filter_2.webp
tests\Images\Input\Webp\alpha_filter_2_method_0.webp = tests\Images\Input\Webp\alpha_filter_2_method_0.webp
tests\Images\Input\Webp\alpha_filter_2_method_1.webp = tests\Images\Input\Webp\alpha_filter_2_method_1.webp
tests\Images\Input\Webp\alpha_filter_3.webp = tests\Images\Input\Webp\alpha_filter_3.webp
tests\Images\Input\Webp\alpha_filter_3_method_0.webp = tests\Images\Input\Webp\alpha_filter_3_method_0.webp
tests\Images\Input\Webp\alpha_filter_3_method_1.webp = tests\Images\Input\Webp\alpha_filter_3_method_1.webp
tests\Images\Input\Webp\alpha_no_compression.webp = tests\Images\Input\Webp\alpha_no_compression.webp
tests\Images\Input\Webp\animated-webp.webp = tests\Images\Input\Webp\animated-webp.webp
tests\Images\Input\Webp\animated2.webp = tests\Images\Input\Webp\animated2.webp
tests\Images\Input\Webp\animated3.webp = tests\Images\Input\Webp\animated3.webp
tests\Images\Input\Webp\animated_lossy.webp = tests\Images\Input\Webp\animated_lossy.webp
tests\Images\Input\Webp\bad_palette_index.webp = tests\Images\Input\Webp\bad_palette_index.webp
tests\Images\Input\Webp\big_endian_bug_393.webp = tests\Images\Input\Webp\big_endian_bug_393.webp
tests\Images\Input\Webp\bike_lossless.webp = tests\Images\Input\Webp\bike_lossless.webp
tests\Images\Input\Webp\bike_lossless_small.webp = tests\Images\Input\Webp\bike_lossless_small.webp
tests\Images\Input\Webp\bike_lossy.webp = tests\Images\Input\Webp\bike_lossy.webp
tests\Images\Input\Webp\bike_lossy_complex_filter.webp = tests\Images\Input\Webp\bike_lossy_complex_filter.webp
tests\Images\Input\Webp\bryce.webp = tests\Images\Input\Webp\bryce.webp
tests\Images\Input\Webp\bug3.webp = tests\Images\Input\Webp\bug3.webp
tests\Images\Input\Webp\color_cache_bits_11.webp = tests\Images\Input\Webp\color_cache_bits_11.webp
tests\Images\Input\Webp\earth_lossless.webp = tests\Images\Input\Webp\earth_lossless.webp
tests\Images\Input\Webp\earth_lossy.webp = tests\Images\Input\Webp\earth_lossy.webp
tests\Images\Input\Webp\exif_lossless.webp = tests\Images\Input\Webp\exif_lossless.webp
tests\Images\Input\Webp\exif_lossy.webp = tests\Images\Input\Webp\exif_lossy.webp
tests\Images\Input\Webp\flag_of_germany.png = tests\Images\Input\Webp\flag_of_germany.png
tests\Images\Input\Webp\lossless1.webp = tests\Images\Input\Webp\lossless1.webp
tests\Images\Input\Webp\lossless2.webp = tests\Images\Input\Webp\lossless2.webp
tests\Images\Input\Webp\lossless3.webp = tests\Images\Input\Webp\lossless3.webp
tests\Images\Input\Webp\lossless4.webp = tests\Images\Input\Webp\lossless4.webp
tests\Images\Input\Webp\lossless_alpha_small.webp = tests\Images\Input\Webp\lossless_alpha_small.webp
tests\Images\Input\Webp\lossless_big_random_alpha.webp = tests\Images\Input\Webp\lossless_big_random_alpha.webp
tests\Images\Input\Webp\lossless_color_transform.bmp = tests\Images\Input\Webp\lossless_color_transform.bmp
tests\Images\Input\Webp\lossless_color_transform.pam = tests\Images\Input\Webp\lossless_color_transform.pam
tests\Images\Input\Webp\lossless_color_transform.pgm = tests\Images\Input\Webp\lossless_color_transform.pgm
tests\Images\Input\Webp\lossless_color_transform.ppm = tests\Images\Input\Webp\lossless_color_transform.ppm
tests\Images\Input\Webp\lossless_color_transform.tiff = tests\Images\Input\Webp\lossless_color_transform.tiff
tests\Images\Input\Webp\lossless_color_transform.webp = tests\Images\Input\Webp\lossless_color_transform.webp
tests\Images\Input\Webp\lossless_vec_1_0.webp = tests\Images\Input\Webp\lossless_vec_1_0.webp
tests\Images\Input\Webp\lossless_vec_1_1.webp = tests\Images\Input\Webp\lossless_vec_1_1.webp
tests\Images\Input\Webp\lossless_vec_1_10.webp = tests\Images\Input\Webp\lossless_vec_1_10.webp
tests\Images\Input\Webp\lossless_vec_1_11.webp = tests\Images\Input\Webp\lossless_vec_1_11.webp
tests\Images\Input\Webp\lossless_vec_1_12.webp = tests\Images\Input\Webp\lossless_vec_1_12.webp
tests\Images\Input\Webp\lossless_vec_1_13.webp = tests\Images\Input\Webp\lossless_vec_1_13.webp
tests\Images\Input\Webp\lossless_vec_1_14.webp = tests\Images\Input\Webp\lossless_vec_1_14.webp
tests\Images\Input\Webp\lossless_vec_1_15.webp = tests\Images\Input\Webp\lossless_vec_1_15.webp
tests\Images\Input\Webp\lossless_vec_1_2.webp = tests\Images\Input\Webp\lossless_vec_1_2.webp
tests\Images\Input\Webp\lossless_vec_1_3.webp = tests\Images\Input\Webp\lossless_vec_1_3.webp
tests\Images\Input\Webp\lossless_vec_1_4.webp = tests\Images\Input\Webp\lossless_vec_1_4.webp
tests\Images\Input\Webp\lossless_vec_1_5.webp = tests\Images\Input\Webp\lossless_vec_1_5.webp
tests\Images\Input\Webp\lossless_vec_1_6.webp = tests\Images\Input\Webp\lossless_vec_1_6.webp
tests\Images\Input\Webp\lossless_vec_1_7.webp = tests\Images\Input\Webp\lossless_vec_1_7.webp
tests\Images\Input\Webp\lossless_vec_1_8.webp = tests\Images\Input\Webp\lossless_vec_1_8.webp
tests\Images\Input\Webp\lossless_vec_1_9.webp = tests\Images\Input\Webp\lossless_vec_1_9.webp
tests\Images\Input\Webp\lossless_vec_2_0.webp = tests\Images\Input\Webp\lossless_vec_2_0.webp
tests\Images\Input\Webp\lossless_vec_2_1.webp = tests\Images\Input\Webp\lossless_vec_2_1.webp
tests\Images\Input\Webp\lossless_vec_2_10.webp = tests\Images\Input\Webp\lossless_vec_2_10.webp
tests\Images\Input\Webp\lossless_vec_2_11.webp = tests\Images\Input\Webp\lossless_vec_2_11.webp
tests\Images\Input\Webp\lossless_vec_2_12.webp = tests\Images\Input\Webp\lossless_vec_2_12.webp
tests\Images\Input\Webp\lossless_vec_2_13.webp = tests\Images\Input\Webp\lossless_vec_2_13.webp
tests\Images\Input\Webp\lossless_vec_2_14.webp = tests\Images\Input\Webp\lossless_vec_2_14.webp
tests\Images\Input\Webp\lossless_vec_2_15.webp = tests\Images\Input\Webp\lossless_vec_2_15.webp
tests\Images\Input\Webp\lossless_vec_2_2.webp = tests\Images\Input\Webp\lossless_vec_2_2.webp
tests\Images\Input\Webp\lossless_vec_2_3.webp = tests\Images\Input\Webp\lossless_vec_2_3.webp
tests\Images\Input\Webp\lossless_vec_2_4.webp = tests\Images\Input\Webp\lossless_vec_2_4.webp
tests\Images\Input\Webp\lossless_vec_2_5.webp = tests\Images\Input\Webp\lossless_vec_2_5.webp
tests\Images\Input\Webp\lossless_vec_2_6.webp = tests\Images\Input\Webp\lossless_vec_2_6.webp
tests\Images\Input\Webp\lossless_vec_2_7.webp = tests\Images\Input\Webp\lossless_vec_2_7.webp
tests\Images\Input\Webp\lossless_vec_2_8.webp = tests\Images\Input\Webp\lossless_vec_2_8.webp
tests\Images\Input\Webp\lossless_vec_2_9.webp = tests\Images\Input\Webp\lossless_vec_2_9.webp
tests\Images\Input\Webp\lossless_vec_list.txt = tests\Images\Input\Webp\lossless_vec_list.txt
tests\Images\Input\Webp\lossless_with_iccp.webp = tests\Images\Input\Webp\lossless_with_iccp.webp
tests\Images\Input\Webp\lossy_alpha1.webp = tests\Images\Input\Webp\lossy_alpha1.webp
tests\Images\Input\Webp\lossy_alpha2.webp = tests\Images\Input\Webp\lossy_alpha2.webp
tests\Images\Input\Webp\lossy_alpha3.webp = tests\Images\Input\Webp\lossy_alpha3.webp
tests\Images\Input\Webp\lossy_alpha4.webp = tests\Images\Input\Webp\lossy_alpha4.webp
tests\Images\Input\Webp\lossy_extreme_probabilities.webp = tests\Images\Input\Webp\lossy_extreme_probabilities.webp
tests\Images\Input\Webp\lossy_q0_f100.webp = tests\Images\Input\Webp\lossy_q0_f100.webp
tests\Images\Input\Webp\lossy_with_iccp.webp = tests\Images\Input\Webp\lossy_with_iccp.webp
tests\Images\Input\Webp\near_lossless_75.webp = tests\Images\Input\Webp\near_lossless_75.webp
tests\Images\Input\Webp\peak.png = tests\Images\Input\Webp\peak.png
tests\Images\Input\Webp\rgb_pattern_100x100.png = tests\Images\Input\Webp\rgb_pattern_100x100.png
tests\Images\Input\Webp\rgb_pattern_63x63.png = tests\Images\Input\Webp\rgb_pattern_63x63.png
tests\Images\Input\Webp\rgb_pattern_80x80.png = tests\Images\Input\Webp\rgb_pattern_80x80.png
tests\Images\Input\Webp\segment01.webp = tests\Images\Input\Webp\segment01.webp
tests\Images\Input\Webp\segment02.webp = tests\Images\Input\Webp\segment02.webp
tests\Images\Input\Webp\segment03.webp = tests\Images\Input\Webp\segment03.webp
tests\Images\Input\Webp\small_13x1.webp = tests\Images\Input\Webp\small_13x1.webp
tests\Images\Input\Webp\small_1x1.webp = tests\Images\Input\Webp\small_1x1.webp
tests\Images\Input\Webp\small_1x13.webp = tests\Images\Input\Webp\small_1x13.webp
tests\Images\Input\Webp\small_31x13.webp = tests\Images\Input\Webp\small_31x13.webp
tests\Images\Input\Webp\sticker.webp = tests\Images\Input\Webp\sticker.webp
tests\Images\Input\Webp\test-nostrong.webp = tests\Images\Input\Webp\test-nostrong.webp
tests\Images\Input\Webp\test.webp = tests\Images\Input\Webp\test.webp
tests\Images\Input\Webp\testpattern_opaque.png = tests\Images\Input\Webp\testpattern_opaque.png
tests\Images\Input\Webp\testpattern_opaque_small.png = tests\Images\Input\Webp\testpattern_opaque_small.png
tests\Images\Input\Webp\very_short.webp = tests\Images\Input\Webp\very_short.webp
tests\Images\Input\Webp\vp80-00-comprehensive-001.webp = tests\Images\Input\Webp\vp80-00-comprehensive-001.webp
tests\Images\Input\Webp\vp80-00-comprehensive-002.webp = tests\Images\Input\Webp\vp80-00-comprehensive-002.webp
tests\Images\Input\Webp\vp80-00-comprehensive-003.webp = tests\Images\Input\Webp\vp80-00-comprehensive-003.webp
tests\Images\Input\Webp\vp80-00-comprehensive-004.webp = tests\Images\Input\Webp\vp80-00-comprehensive-004.webp
tests\Images\Input\Webp\vp80-00-comprehensive-005.webp = tests\Images\Input\Webp\vp80-00-comprehensive-005.webp
tests\Images\Input\Webp\vp80-00-comprehensive-006.webp = tests\Images\Input\Webp\vp80-00-comprehensive-006.webp
tests\Images\Input\Webp\vp80-00-comprehensive-007.webp = tests\Images\Input\Webp\vp80-00-comprehensive-007.webp
tests\Images\Input\Webp\vp80-00-comprehensive-008.webp = tests\Images\Input\Webp\vp80-00-comprehensive-008.webp
tests\Images\Input\Webp\vp80-00-comprehensive-009.webp = tests\Images\Input\Webp\vp80-00-comprehensive-009.webp
tests\Images\Input\Webp\vp80-00-comprehensive-010.webp = tests\Images\Input\Webp\vp80-00-comprehensive-010.webp
tests\Images\Input\Webp\vp80-00-comprehensive-011.webp = tests\Images\Input\Webp\vp80-00-comprehensive-011.webp
tests\Images\Input\Webp\vp80-00-comprehensive-012.webp = tests\Images\Input\Webp\vp80-00-comprehensive-012.webp
tests\Images\Input\Webp\vp80-00-comprehensive-013.webp = tests\Images\Input\Webp\vp80-00-comprehensive-013.webp
tests\Images\Input\Webp\vp80-00-comprehensive-014.webp = tests\Images\Input\Webp\vp80-00-comprehensive-014.webp
tests\Images\Input\Webp\vp80-00-comprehensive-015.webp = tests\Images\Input\Webp\vp80-00-comprehensive-015.webp
tests\Images\Input\Webp\vp80-00-comprehensive-016.webp = tests\Images\Input\Webp\vp80-00-comprehensive-016.webp
tests\Images\Input\Webp\vp80-00-comprehensive-017.webp = tests\Images\Input\Webp\vp80-00-comprehensive-017.webp
tests\Images\Input\Webp\vp80-01-intra-1400.webp = tests\Images\Input\Webp\vp80-01-intra-1400.webp
tests\Images\Input\Webp\vp80-01-intra-1411.webp = tests\Images\Input\Webp\vp80-01-intra-1411.webp
tests\Images\Input\Webp\vp80-01-intra-1416.webp = tests\Images\Input\Webp\vp80-01-intra-1416.webp
tests\Images\Input\Webp\vp80-01-intra-1417.webp = tests\Images\Input\Webp\vp80-01-intra-1417.webp
tests\Images\Input\Webp\vp80-02-inter-1402.webp = tests\Images\Input\Webp\vp80-02-inter-1402.webp
tests\Images\Input\Webp\vp80-02-inter-1412.webp = tests\Images\Input\Webp\vp80-02-inter-1412.webp
tests\Images\Input\Webp\vp80-02-inter-1418.webp = tests\Images\Input\Webp\vp80-02-inter-1418.webp
tests\Images\Input\Webp\vp80-02-inter-1424.webp = tests\Images\Input\Webp\vp80-02-inter-1424.webp
tests\Images\Input\Webp\vp80-03-segmentation-1401.webp = tests\Images\Input\Webp\vp80-03-segmentation-1401.webp
tests\Images\Input\Webp\vp80-03-segmentation-1403.webp = tests\Images\Input\Webp\vp80-03-segmentation-1403.webp
tests\Images\Input\Webp\vp80-03-segmentation-1407.webp = tests\Images\Input\Webp\vp80-03-segmentation-1407.webp
tests\Images\Input\Webp\vp80-03-segmentation-1408.webp = tests\Images\Input\Webp\vp80-03-segmentation-1408.webp
tests\Images\Input\Webp\vp80-03-segmentation-1409.webp = tests\Images\Input\Webp\vp80-03-segmentation-1409.webp
tests\Images\Input\Webp\vp80-03-segmentation-1410.webp = tests\Images\Input\Webp\vp80-03-segmentation-1410.webp
tests\Images\Input\Webp\vp80-03-segmentation-1413.webp = tests\Images\Input\Webp\vp80-03-segmentation-1413.webp
tests\Images\Input\Webp\vp80-03-segmentation-1414.webp = tests\Images\Input\Webp\vp80-03-segmentation-1414.webp
tests\Images\Input\Webp\vp80-03-segmentation-1415.webp = tests\Images\Input\Webp\vp80-03-segmentation-1415.webp
tests\Images\Input\Webp\vp80-03-segmentation-1425.webp = tests\Images\Input\Webp\vp80-03-segmentation-1425.webp
tests\Images\Input\Webp\vp80-03-segmentation-1426.webp = tests\Images\Input\Webp\vp80-03-segmentation-1426.webp
tests\Images\Input\Webp\vp80-03-segmentation-1427.webp = tests\Images\Input\Webp\vp80-03-segmentation-1427.webp
tests\Images\Input\Webp\vp80-03-segmentation-1432.webp = tests\Images\Input\Webp\vp80-03-segmentation-1432.webp
tests\Images\Input\Webp\vp80-03-segmentation-1435.webp = tests\Images\Input\Webp\vp80-03-segmentation-1435.webp
tests\Images\Input\Webp\vp80-03-segmentation-1436.webp = tests\Images\Input\Webp\vp80-03-segmentation-1436.webp
tests\Images\Input\Webp\vp80-03-segmentation-1437.webp = tests\Images\Input\Webp\vp80-03-segmentation-1437.webp
tests\Images\Input\Webp\vp80-03-segmentation-1441.webp = tests\Images\Input\Webp\vp80-03-segmentation-1441.webp
tests\Images\Input\Webp\vp80-03-segmentation-1442.webp = tests\Images\Input\Webp\vp80-03-segmentation-1442.webp
tests\Images\Input\Webp\vp80-04-partitions-1404.webp = tests\Images\Input\Webp\vp80-04-partitions-1404.webp
tests\Images\Input\Webp\vp80-04-partitions-1405.webp = tests\Images\Input\Webp\vp80-04-partitions-1405.webp
tests\Images\Input\Webp\vp80-04-partitions-1406.webp = tests\Images\Input\Webp\vp80-04-partitions-1406.webp
tests\Images\Input\Webp\vp80-05-sharpness-1428.webp = tests\Images\Input\Webp\vp80-05-sharpness-1428.webp
tests\Images\Input\Webp\vp80-05-sharpness-1429.webp = tests\Images\Input\Webp\vp80-05-sharpness-1429.webp
tests\Images\Input\Webp\vp80-05-sharpness-1430.webp = tests\Images\Input\Webp\vp80-05-sharpness-1430.webp
tests\Images\Input\Webp\vp80-05-sharpness-1431.webp = tests\Images\Input\Webp\vp80-05-sharpness-1431.webp
tests\Images\Input\Webp\vp80-05-sharpness-1433.webp = tests\Images\Input\Webp\vp80-05-sharpness-1433.webp
tests\Images\Input\Webp\vp80-05-sharpness-1434.webp = tests\Images\Input\Webp\vp80-05-sharpness-1434.webp
tests\Images\Input\Webp\vp80-05-sharpness-1438.webp = tests\Images\Input\Webp\vp80-05-sharpness-1438.webp
tests\Images\Input\Webp\vp80-05-sharpness-1439.webp = tests\Images\Input\Webp\vp80-05-sharpness-1439.webp
tests\Images\Input\Webp\vp80-05-sharpness-1440.webp = tests\Images\Input\Webp\vp80-05-sharpness-1440.webp
tests\Images\Input\Webp\vp80-05-sharpness-1443.webp = tests\Images\Input\Webp\vp80-05-sharpness-1443.webp
tests\Images\Input\Webp\yuv_test.png = tests\Images\Input\Webp\yuv_test.png
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests", "tests\ImageSharp.Tests\ImageSharp.Tests.csproj", "{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests", "tests\ImageSharp.Tests\ImageSharp.Tests.csproj", "{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Benchmarks", "tests\ImageSharp.Benchmarks\ImageSharp.Benchmarks.csproj", "{2BF743D8-2A06-412D-96D7-F448F00C5EA5}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Benchmarks", "tests\ImageSharp.Benchmarks\ImageSharp.Benchmarks.csproj", "{2BF743D8-2A06-412D-96D7-F448F00C5EA5}"
@ -404,6 +569,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issues", "issues", "{670DD4
tests\Images\Input\Png\issues\Issue_1127.png = tests\Images\Input\Png\issues\Issue_1127.png tests\Images\Input\Png\issues\Issue_1127.png = tests\Images\Input\Png\issues\Issue_1127.png
tests\Images\Input\Png\issues\Issue_1177_1.png = tests\Images\Input\Png\issues\Issue_1177_1.png tests\Images\Input\Png\issues\Issue_1177_1.png = tests\Images\Input\Png\issues\Issue_1177_1.png
tests\Images\Input\Png\issues\Issue_1177_2.png = tests\Images\Input\Png\issues\Issue_1177_2.png tests\Images\Input\Png\issues\Issue_1177_2.png = tests\Images\Input\Png\issues\Issue_1177_2.png
tests\Images\Input\Png\issues\Issue_1765_Net6DeflateStreamRead.png = tests\Images\Input\Png\issues\Issue_1765_Net6DeflateStreamRead.png
tests\Images\Input\Png\issues\Issue_410.png = tests\Images\Input\Png\issues\Issue_410.png tests\Images\Input\Png\issues\Issue_410.png = tests\Images\Input\Png\issues\Issue_410.png
tests\Images\Input\Png\issues\Issue_935.png = tests\Images\Input\Png\issues\Issue_935.png tests\Images\Input\Png\issues\Issue_935.png = tests\Images\Input\Png\issues\Issue_935.png
EndProjectSection EndProjectSection
@ -480,61 +646,43 @@ Global
EndGlobalSection EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64 Debug-InnerLoop|Any CPU = Debug-InnerLoop|Any CPU
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64 Release-InnerLoop|Any CPU = Release-InnerLoop|Any CPU
Release|x86 = Release|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
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|Any CPU.Build.0 = Debug|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x64.ActiveCfg = Debug|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x64.Build.0 = Debug|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|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}.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-InnerLoop|Any CPU.ActiveCfg = Release-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x64.Build.0 = Release|Any CPU {2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|Any CPU.Build.0 = Release-InnerLoop|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
{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-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x64.Build.0 = Debug|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|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}.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-InnerLoop|Any CPU.ActiveCfg = Release-InnerLoop|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x64.Build.0 = Release|Any CPU {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|Any CPU.Build.0 = Release-InnerLoop|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
{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-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x64.Build.0 = Debug|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|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}.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-InnerLoop|Any CPU.ActiveCfg = Release-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x64.Build.0 = Release|Any CPU {2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|Any CPU.Build.0 = Release-InnerLoop|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
{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-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x64.Build.0 = Debug|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|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}.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-InnerLoop|Any CPU.ActiveCfg = Release-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|x64.Build.0 = Release|Any CPU {FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|Any CPU.Build.0 = Release-InnerLoop|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
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -557,6 +705,7 @@ Global
{6458AFCB-A159-47D5-8F2B-50C95C0915E0} = {DB21FED7-E8CB-4B00-9EB2-9144D32A590A} {6458AFCB-A159-47D5-8F2B-50C95C0915E0} = {DB21FED7-E8CB-4B00-9EB2-9144D32A590A}
{39F5197B-CF6C-41A5-9739-7F97E78BB104} = {6458AFCB-A159-47D5-8F2B-50C95C0915E0} {39F5197B-CF6C-41A5-9739-7F97E78BB104} = {6458AFCB-A159-47D5-8F2B-50C95C0915E0}
{E1C42A6F-913B-4A7B-B1A8-2BB62843B254} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} {E1C42A6F-913B-4A7B-B1A8-2BB62843B254} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
{983A31E2-5E26-4058-BD6E-03B4922D4BBF} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
{2BF743D8-2A06-412D-96D7-F448F00C5EA5} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {2BF743D8-2A06-412D-96D7-F448F00C5EA5} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
{C0D7754B-5277-438E-ABEB-2BA34401B5A7} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D} {C0D7754B-5277-438E-ABEB-2BA34401B5A7} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D}

47
README.md

@ -1,4 +1,4 @@
<h1 align="center"> <h1 align="center">
<img src="https://github.com/SixLabors/Branding/raw/master/icons/imagesharp/sixlabors.imagesharp.svg?sanitize=true" alt="SixLabors.ImageSharp" width="256"/> <img src="https://github.com/SixLabors/Branding/raw/master/icons/imagesharp/sixlabors.imagesharp.svg?sanitize=true" alt="SixLabors.ImageSharp" width="256"/>
<br/> <br/>
@ -26,9 +26,16 @@ Built against [.NET Standard 1.3](https://docs.microsoft.com/en-us/dotnet/standa
## License ## License
- ImageSharp is licensed under the [Apache License, Version 2.0](https://opensource.org/licenses/Apache-2.0) - ImageSharp is licensed under the [Apache License, Version 2.0](https://opensource.org/licenses/Apache-2.0)
- An alternative Commercial License can be purchased for projects and applications requiring support. - An alternative Commercial Support License can be purchased **for projects and applications requiring support**.
Please visit https://sixlabors.com/pricing for details. Please visit https://sixlabors.com/pricing for details.
## Support Six Labors
Support the efforts of the development of the Six Labors projects.
- [Purchase a Commercial Support License :heart:](https://sixlabors.com/pricing/)
- [Become a sponsor via GitHub Sponsors :heart:]( https://github.com/sponsors/SixLabors)
- [Become a sponsor via Open Collective :heart:](https://opencollective.com/sixlabors)
## Documentation ## Documentation
- [Detailed documentation](https://sixlabors.github.io/docs/) for the ImageSharp API is available. This includes additional conceptual documentation to help you get started. - [Detailed documentation](https://sixlabors.github.io/docs/) for the ImageSharp API is available. This includes additional conceptual documentation to help you get started.
@ -57,7 +64,7 @@ If you prefer, you can compile ImageSharp yourself (please do and help!)
- Using [Visual Studio 2019](https://visualstudio.microsoft.com/vs/) - Using [Visual Studio 2019](https://visualstudio.microsoft.com/vs/)
- Make sure you have the latest version installed - Make sure you have the latest version installed
- Make sure you have [the .NET Core 3.1 SDK](https://www.microsoft.com/net/core#windows) installed - Make sure you have [the .NET 5 SDK](https://www.microsoft.com/net/core#windows) installed
Alternatively, you can work from command line and/or with a lightweight editor on **both Linux/Unix and Windows**: Alternatively, you can work from command line and/or with a lightweight editor on **both Linux/Unix and Windows**:
@ -96,40 +103,6 @@ Please... Spread the word, contribute algorithms, submit performance improvement
- [Scott Williams](https://github.com/tocsoft) - [Scott Williams](https://github.com/tocsoft)
- [Brian Popow](https://github.com/brianpopow) - [Brian Popow](https://github.com/brianpopow)
## Sponsor Six Labors
Support the efforts of the development of the Six Labors projects. [[Become a sponsor :heart:](https://opencollective.com/sixlabors#sponsor)]
### Platinum Sponsors
Become a platinum sponsor with a monthly donation of $2000 (providing 32 hours of maintenance and development) and get 2 hours of dedicated support (remote support available through chat or screen-sharing) per month.
In addition you get your logo (large) on our README on GitHub and the home page (large) of sixlabors.com
<a href="https://opencollective.com/sixlabors/tiers/platinum-sponsors/0/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/platinum-sponsors/0/avatar.svg?avatarHeight=192"></a>
### Gold Sponsors
Become a gold sponsor with a monthly donation of $1000 (providing 16 hours of maintenance and development) and get 1 hour of dedicated support (remote support available through chat or screen-sharing) per month.
In addition you get your logo (large) on our README on GitHub and the home page (medium) of sixlabors.com
<a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/0/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/0/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/1/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/1/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/2/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/2/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/3/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/3/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/4/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/4/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/5/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/5/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/6/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/6/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/7/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/7/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/8/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/8/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/9/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/9/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/10/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/10/avatar.svg?avatarHeight=156"></a>
### Silver Sponsors
Become a silver sponsor with a monthly donation of $500 (providing 8 hours of maintenance and development) and get your logo (medium) on our README on GitHub and the product pages of sixlabors.com
<a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/0/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/0/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/1/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/1/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/2/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/2/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/3/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/3/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/4/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/4/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/5/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/5/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/6/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/6/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/7/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/7/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/8/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/8/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/9/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/9/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/10/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/10/avatar.svg?avatarHeight=128"></a>
### Bronze Sponsors
Become a bronze sponsor with a monthly donation of $100 and get your logo (small) on our README on GitHub.
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/0/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/0/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/1/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/1/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/2/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/2/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/3/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/3/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/4/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/4/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/5/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/5/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/6/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/6/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/7/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/7/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/8/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/8/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/9/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/9/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/10/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/10/avatar.svg?avatarHeight=96"></a>

11
codecov.yml

@ -9,3 +9,14 @@ codecov:
# Avoid Report Expired # Avoid Report Expired
# https://docs.codecov.io/docs/codecov-yaml#section-expired-reports # https://docs.codecov.io/docs/codecov-yaml#section-expired-reports
max_report_age: off max_report_age: off
coverage:
# Use integer precision
# https://docs.codecov.com/docs/codecovyml-reference#coverageprecision
precision: 0
# Explicitly control coverage status checks
# https://docs.codecov.com/docs/commit-status#disabling-a-status
status:
project: on
patch: off

2
shared-infrastructure

@ -1 +1 @@
Subproject commit 06a733983486638b9e38197c7c6eb197ecac43e6 Subproject commit 33cb12ca77f919b44de56f344d2627cc2a108c3a

12
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -12,6 +12,8 @@ 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.Png;
using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
@ -56,7 +58,7 @@ namespace SixLabors.ImageSharp.Advanced
/// necessary methods to complete the SaveAsGif call. That's it, otherwise you should NEVER need this method!!! /// necessary methods to complete the SaveAsGif call. That's it, otherwise you should NEVER need this method!!!
/// </remarks> /// </remarks>
[Preserve] [Preserve]
private static void SeedEverything() private static void SeedPixelFormats()
{ {
try try
{ {
@ -194,11 +196,13 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileImageEncoderInternals<TPixel>() private static void AotCompileImageEncoderInternals<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
default(WebpEncoderCore).Encode<TPixel>(default, default, default);
default(BmpEncoderCore).Encode<TPixel>(default, default, default); default(BmpEncoderCore).Encode<TPixel>(default, default, default);
default(GifEncoderCore).Encode<TPixel>(default, default, default); default(GifEncoderCore).Encode<TPixel>(default, default, default);
default(JpegEncoderCore).Encode<TPixel>(default, default, default); default(JpegEncoderCore).Encode<TPixel>(default, default, default);
default(PngEncoderCore).Encode<TPixel>(default, default, default); default(PngEncoderCore).Encode<TPixel>(default, default, default);
default(TgaEncoderCore).Encode<TPixel>(default, default, default); default(TgaEncoderCore).Encode<TPixel>(default, default, default);
default(TiffEncoderCore).Encode<TPixel>(default, default, default);
} }
/// <summary> /// <summary>
@ -209,11 +213,13 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileImageDecoderInternals<TPixel>() private static void AotCompileImageDecoderInternals<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
default(WebpDecoderCore).Decode<TPixel>(default, default, default);
default(BmpDecoderCore).Decode<TPixel>(default, default, default); default(BmpDecoderCore).Decode<TPixel>(default, default, default);
default(GifDecoderCore).Decode<TPixel>(default, default, default); default(GifDecoderCore).Decode<TPixel>(default, default, default);
default(JpegDecoderCore).Decode<TPixel>(default, default, default); default(JpegDecoderCore).Decode<TPixel>(default, default, default);
default(PngDecoderCore).Decode<TPixel>(default, default, default); default(PngDecoderCore).Decode<TPixel>(default, default, default);
default(TgaDecoderCore).Decode<TPixel>(default, default, default); default(TgaDecoderCore).Decode<TPixel>(default, default, default);
default(TiffDecoderCore).Decode<TPixel>(default, default, default);
} }
/// <summary> /// <summary>
@ -224,11 +230,13 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileImageEncoders<TPixel>() private static void AotCompileImageEncoders<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
AotCompileImageEncoder<TPixel, WebpEncoder>();
AotCompileImageEncoder<TPixel, BmpEncoder>(); AotCompileImageEncoder<TPixel, BmpEncoder>();
AotCompileImageEncoder<TPixel, GifEncoder>(); AotCompileImageEncoder<TPixel, GifEncoder>();
AotCompileImageEncoder<TPixel, JpegEncoder>(); AotCompileImageEncoder<TPixel, JpegEncoder>();
AotCompileImageEncoder<TPixel, PngEncoder>(); AotCompileImageEncoder<TPixel, PngEncoder>();
AotCompileImageEncoder<TPixel, TgaEncoder>(); AotCompileImageEncoder<TPixel, TgaEncoder>();
AotCompileImageEncoder<TPixel, TiffEncoder>();
} }
/// <summary> /// <summary>
@ -239,11 +247,13 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileImageDecoders<TPixel>() private static void AotCompileImageDecoders<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
AotCompileImageDecoder<TPixel, WebpDecoder>();
AotCompileImageDecoder<TPixel, BmpDecoder>(); AotCompileImageDecoder<TPixel, BmpDecoder>();
AotCompileImageDecoder<TPixel, GifDecoder>(); AotCompileImageDecoder<TPixel, GifDecoder>();
AotCompileImageDecoder<TPixel, JpegDecoder>(); AotCompileImageDecoder<TPixel, JpegDecoder>();
AotCompileImageDecoder<TPixel, PngDecoder>(); AotCompileImageDecoder<TPixel, PngDecoder>();
AotCompileImageDecoder<TPixel, TgaDecoder>(); AotCompileImageDecoder<TPixel, TgaDecoder>();
AotCompileImageDecoder<TPixel, TiffDecoder>();
} }
/// <summary> /// <summary>

1
src/ImageSharp/Advanced/ParallelExecutionSettings.cs

@ -3,7 +3,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Advanced namespace SixLabors.ImageSharp.Advanced

151
src/ImageSharp/Color/Color.Conversions.cs

@ -3,7 +3,6 @@
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp
@ -18,56 +17,118 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="pixel">The <see cref="Rgba64"/> containing the color information.</param> /// <param name="pixel">The <see cref="Rgba64"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Rgba64 pixel) => this.data = pixel; public Color(Rgba64 pixel)
{
this.data = pixel;
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The <see cref="Rgb48"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(Rgb48 pixel)
{
this.data = new Rgba64(pixel.R, pixel.G, pixel.B, ushort.MaxValue);
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The <see cref="La32"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(La32 pixel)
{
this.data = new Rgba64(pixel.L, pixel.L, pixel.L, pixel.A);
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The <see cref="L16"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(L16 pixel)
{
this.data = new Rgba64(pixel.PackedValue, pixel.PackedValue, pixel.PackedValue, ushort.MaxValue);
this.boxedHighPrecisionPixel = null;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct. /// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary> /// </summary>
/// <param name="pixel">The <see cref="Rgba32"/> containing the color information.</param> /// <param name="pixel">The <see cref="Rgba32"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Rgba32 pixel) => this.data = new Rgba64(pixel); public Color(Rgba32 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct. /// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary> /// </summary>
/// <param name="pixel">The <see cref="Argb32"/> containing the color information.</param> /// <param name="pixel">The <see cref="Argb32"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Argb32 pixel) => this.data = new Rgba64(pixel); public Color(Argb32 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct. /// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary> /// </summary>
/// <param name="pixel">The <see cref="Bgra32"/> containing the color information.</param> /// <param name="pixel">The <see cref="Bgra32"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Bgra32 pixel) => this.data = new Rgba64(pixel); public Color(Bgra32 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct. /// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary> /// </summary>
/// <param name="pixel">The <see cref="Rgb24"/> containing the color information.</param> /// <param name="pixel">The <see cref="Rgb24"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Rgb24 pixel) => this.data = new Rgba64(pixel); public Color(Rgb24 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct. /// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary> /// </summary>
/// <param name="pixel">The <see cref="Bgr24"/> containing the color information.</param> /// <param name="pixel">The <see cref="Bgr24"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Bgr24 pixel) => this.data = new Rgba64(pixel); public Color(Bgr24 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct. /// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary> /// </summary>
/// <param name="vector">The <see cref="Vector4"/> containing the color information.</param> /// <param name="vector">The <see cref="Vector4"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Vector4 vector) => this.data = new Rgba64(vector); public Color(Vector4 vector)
{
vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One);
this.boxedHighPrecisionPixel = new RgbaVector(vector.X, vector.Y, vector.Z, vector.W);
this.data = default;
}
/// <summary> /// <summary>
/// Converts a <see cref="Color"/> to <see cref="Vector4"/>. /// Converts a <see cref="Color"/> to <see cref="Vector4"/>.
/// </summary> /// </summary>
/// <param name="color">The <see cref="Color"/>.</param> /// <param name="color">The <see cref="Color"/>.</param>
/// <returns>The <see cref="Vector4"/>.</returns> /// <returns>The <see cref="Vector4"/>.</returns>
public static explicit operator Vector4(Color color) => color.data.ToVector4(); public static explicit operator Vector4(Color color) => color.ToVector4();
/// <summary> /// <summary>
/// Converts an <see cref="Vector4"/> to <see cref="Color"/>. /// Converts an <see cref="Vector4"/> to <see cref="Color"/>.
@ -75,24 +136,82 @@ namespace SixLabors.ImageSharp
/// <param name="source">The <see cref="Vector4"/>.</param> /// <param name="source">The <see cref="Vector4"/>.</param>
/// <returns>The <see cref="Color"/>.</returns> /// <returns>The <see cref="Color"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static explicit operator Color(Vector4 source) => new Color(source); public static explicit operator Color(Vector4 source) => new(source);
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal Rgba32 ToRgba32() => this.data.ToRgba32(); internal Rgba32 ToRgba32()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToRgba32();
}
Rgba32 value = default;
this.boxedHighPrecisionPixel.ToRgba32(ref value);
return value;
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal Bgra32 ToBgra32() => this.data.ToBgra32(); internal Bgra32 ToBgra32()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToBgra32();
}
Bgra32 value = default;
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return value;
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal Argb32 ToArgb32() => this.data.ToArgb32(); internal Argb32 ToArgb32()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToArgb32();
}
Argb32 value = default;
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return value;
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal Rgb24 ToRgb24() => this.data.ToRgb24(); internal Rgb24 ToRgb24()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToRgb24();
}
Rgb24 value = default;
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return value;
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal Bgr24 ToBgr24() => this.data.ToBgr24(); internal Bgr24 ToBgr24()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToBgr24();
}
Bgr24 value = default;
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return value;
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal Vector4 ToVector4() => this.data.ToVector4(); internal Vector4 ToVector4()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToScaledVector4();
}
return this.boxedHighPrecisionPixel.ToScaledVector4();
}
} }
} }

106
src/ImageSharp/Color/Color.cs

@ -4,8 +4,6 @@
using System; using System;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp
@ -22,6 +20,7 @@ namespace SixLabors.ImageSharp
public readonly partial struct Color : IEquatable<Color> public readonly partial struct Color : IEquatable<Color>
{ {
private readonly Rgba64 data; private readonly Rgba64 data;
private readonly IPixel boxedHighPrecisionPixel;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private Color(byte r, byte g, byte b, byte a) private Color(byte r, byte g, byte b, byte a)
@ -31,6 +30,8 @@ namespace SixLabors.ImageSharp
ColorNumerics.UpscaleFrom8BitTo16Bit(g), ColorNumerics.UpscaleFrom8BitTo16Bit(g),
ColorNumerics.UpscaleFrom8BitTo16Bit(b), ColorNumerics.UpscaleFrom8BitTo16Bit(b),
ColorNumerics.UpscaleFrom8BitTo16Bit(a)); ColorNumerics.UpscaleFrom8BitTo16Bit(a));
this.boxedHighPrecisionPixel = null;
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
@ -41,6 +42,15 @@ namespace SixLabors.ImageSharp
ColorNumerics.UpscaleFrom8BitTo16Bit(g), ColorNumerics.UpscaleFrom8BitTo16Bit(g),
ColorNumerics.UpscaleFrom8BitTo16Bit(b), ColorNumerics.UpscaleFrom8BitTo16Bit(b),
ushort.MaxValue); ushort.MaxValue);
this.boxedHighPrecisionPixel = null;
}
[MethodImpl(InliningOptions.ShortMethod)]
private Color(IPixel pixel)
{
this.boxedHighPrecisionPixel = pixel;
this.data = default;
} }
/// <summary> /// <summary>
@ -53,13 +63,10 @@ namespace SixLabors.ImageSharp
/// otherwise, false. /// otherwise, false.
/// </returns> /// </returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static bool operator ==(Color left, Color right) public static bool operator ==(Color left, Color right) => left.Equals(right);
{
return left.Equals(right);
}
/// <summary> /// <summary>
/// Checks whether two <see cref="Color"/> structures are equal. /// Checks whether two <see cref="Color"/> structures are not equal.
/// </summary> /// </summary>
/// <param name="left">The left hand <see cref="Color"/> operand.</param> /// <param name="left">The left hand <see cref="Color"/> operand.</param>
/// <param name="right">The right hand <see cref="Color"/> operand.</param> /// <param name="right">The right hand <see cref="Color"/> operand.</param>
@ -68,10 +75,7 @@ namespace SixLabors.ImageSharp
/// otherwise, false. /// otherwise, false.
/// </returns> /// </returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static bool operator !=(Color left, Color right) public static bool operator !=(Color left, Color right) => !left.Equals(right);
{
return !left.Equals(right);
}
/// <summary> /// <summary>
/// Creates a <see cref="Color"/> from RGBA bytes. /// Creates a <see cref="Color"/> from RGBA bytes.
@ -82,7 +86,7 @@ namespace SixLabors.ImageSharp
/// <param name="a">The alpha component (0-255).</param> /// <param name="a">The alpha component (0-255).</param>
/// <returns>The <see cref="Color"/>.</returns> /// <returns>The <see cref="Color"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static Color FromRgba(byte r, byte g, byte b, byte a) => new Color(r, g, b, a); public static Color FromRgba(byte r, byte g, byte b, byte a) => new(r, g, b, a);
/// <summary> /// <summary>
/// Creates a <see cref="Color"/> from RGB bytes. /// Creates a <see cref="Color"/> from RGB bytes.
@ -92,7 +96,46 @@ namespace SixLabors.ImageSharp
/// <param name="b">The blue component (0-255).</param> /// <param name="b">The blue component (0-255).</param>
/// <returns>The <see cref="Color"/>.</returns> /// <returns>The <see cref="Color"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static Color FromRgb(byte r, byte g, byte b) => new Color(r, g, b); public static Color FromRgb(byte r, byte g, byte b) => new(r, g, b);
/// <summary>
/// Creates a <see cref="Color"/> from the given <typeparamref name="TPixel"/>.
/// </summary>
/// <param name="pixel">The pixel to convert from.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Color"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Color FromPixel<TPixel>(TPixel pixel)
where TPixel : unmanaged, IPixel<TPixel>
{
// Avoid boxing in case we can convert to Rgba64 safely and efficently
if (typeof(TPixel) == typeof(Rgba64))
{
return new((Rgba64)(object)pixel);
}
else if (typeof(TPixel) == typeof(Rgb48))
{
return new((Rgb48)(object)pixel);
}
else if (typeof(TPixel) == typeof(La32))
{
return new((La32)(object)pixel);
}
else if (typeof(TPixel) == typeof(L16))
{
return new((L16)(object)pixel);
}
else if (Unsafe.SizeOf<TPixel>() <= Unsafe.SizeOf<Rgba32>())
{
Rgba32 p = default;
pixel.ToRgba32(ref p);
return new(p);
}
else
{
return new(pixel);
}
}
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="Color"/> struct /// Creates a new instance of the <see cref="Color"/> struct
@ -214,7 +257,7 @@ namespace SixLabors.ImageSharp
public override string ToString() => this.ToHex(); public override string ToString() => this.ToHex();
/// <summary> /// <summary>
/// Converts the color instance to a specified <see cref="IPixel{TSelf}"/> type. /// Converts the color instance to a specified <typeparamref name="TPixel"/> type.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel type to convert to.</typeparam> /// <typeparam name="TPixel">The pixel type to convert to.</typeparam>
/// <returns>The pixel value.</returns> /// <returns>The pixel value.</returns>
@ -222,13 +265,18 @@ namespace SixLabors.ImageSharp
public TPixel ToPixel<TPixel>() public TPixel ToPixel<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
TPixel pixel = default; if (this.boxedHighPrecisionPixel is TPixel pixel)
{
return pixel;
}
pixel = default;
pixel.FromRgba64(this.data); pixel.FromRgba64(this.data);
return pixel; return pixel;
} }
/// <summary> /// <summary>
/// Bulk converts a span of <see cref="Color"/> to a span of a specified <see cref="IPixel{TSelf}"/> type. /// Bulk converts a span of <see cref="Color"/> to a span of a specified <typeparamref name="TPixel"/> type.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel type to convert to.</typeparam> /// <typeparam name="TPixel">The pixel type to convert to.</typeparam>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
@ -241,28 +289,38 @@ namespace SixLabors.ImageSharp
Span<TPixel> destination) Span<TPixel> destination)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
ReadOnlySpan<Rgba64> rgba64Span = MemoryMarshal.Cast<Color, Rgba64>(source); Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
PixelOperations<TPixel>.Instance.FromRgba64(configuration, rgba64Span, destination); for (int i = 0; i < source.Length; i++)
{
destination[i] = source[i].ToPixel<TPixel>();
}
} }
/// <inheritdoc /> /// <inheritdoc />
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public bool Equals(Color other) public bool Equals(Color other)
{ {
return this.data.PackedValue == other.data.PackedValue; if (this.boxedHighPrecisionPixel is null && other.boxedHighPrecisionPixel is null)
{
return this.data.PackedValue == other.data.PackedValue;
}
return this.boxedHighPrecisionPixel?.Equals(other.boxedHighPrecisionPixel) == true;
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool Equals(object obj) public override bool Equals(object obj) => obj is Color other && this.Equals(other);
{
return obj is Color other && this.Equals(other);
}
/// <inheritdoc /> /// <inheritdoc />
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public override int GetHashCode() public override int GetHashCode()
{ {
return this.data.PackedValue.GetHashCode(); if (this.boxedHighPrecisionPixel is null)
{
return this.data.PackedValue.GetHashCode();
}
return this.boxedHighPrecisionPixel.GetHashCode();
} }
} }
} }

2
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.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.
using System; using System;

23
src/ImageSharp/Common/ByteOrder.cs

@ -0,0 +1,23 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp
{
/// <summary>
/// The byte order of the data stream.
/// </summary>
public enum ByteOrder
{
/// <summary>
/// The big-endian byte order (Motorola).
/// Most-significant byte comes first, and ends with the least-significant byte.
/// </summary>
BigEndian,
/// <summary>
/// The little-endian byte order (Intel).
/// Least-significant byte comes first and ends with the most-significant byte.
/// </summary>
LittleEndian
}
}

7
src/ImageSharp/Common/Extensions/StreamExtensions.cs

@ -4,7 +4,6 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.IO; using System.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp
{ {
@ -72,12 +71,6 @@ namespace SixLabors.ImageSharp
} }
} }
public static void Read(this Stream stream, IManagedByteBuffer buffer)
=> stream.Read(buffer.Array, 0, buffer.Length());
public static void Write(this Stream stream, IManagedByteBuffer buffer)
=> stream.Write(buffer.Array, 0, buffer.Length());
#if !SUPPORTS_SPAN_STREAM #if !SUPPORTS_SPAN_STREAM
// This is a port of the CoreFX implementation and is MIT Licensed: // This is a port of the CoreFX implementation and is MIT Licensed:
// https://github.com/dotnet/corefx/blob/17300169760c61a90cab8d913636c1058a30a8c1/src/Common/src/CoreLib/System/IO/Stream.cs#L742 // https://github.com/dotnet/corefx/blob/17300169760c61a90cab8d913636c1058a30a8c1/src/Common/src/CoreLib/System/IO/Stream.cs#L742

4
src/ImageSharp/Common/Helpers/DebugGuard.cs

@ -37,7 +37,7 @@ namespace SixLabors
/// <paramref name="target"/> has a different size than <paramref name="other"/> /// <paramref name="target"/> has a different size than <paramref name="other"/>
/// </exception> /// </exception>
[Conditional("DEBUG")] [Conditional("DEBUG")]
public static void MustBeSameSized<T>(Span<T> target, Span<T> other, string parameterName) public static void MustBeSameSized<T>(ReadOnlySpan<T> target, ReadOnlySpan<T> other, string parameterName)
where T : struct where T : struct
{ {
if (target.Length != other.Length) if (target.Length != other.Length)
@ -57,7 +57,7 @@ namespace SixLabors
/// <paramref name="target"/> has less items than <paramref name="minSpan"/> /// <paramref name="target"/> has less items than <paramref name="minSpan"/>
/// </exception> /// </exception>
[Conditional("DEBUG")] [Conditional("DEBUG")]
public static void MustBeSizedAtLeast<T>(Span<T> target, Span<T> minSpan, string parameterName) public static void MustBeSizedAtLeast<T>(ReadOnlySpan<T> target, ReadOnlySpan<T> minSpan, string parameterName)
where T : struct where T : struct
{ {
if (target.Length < minSpan.Length) if (target.Length < minSpan.Length)

21
src/ImageSharp/Common/Helpers/ExifResolutionValues.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Common.Helpers
{
internal readonly struct ExifResolutionValues
{
public ExifResolutionValues(ushort resolutionUnit, double? horizontalResolution, double? verticalResolution)
{
this.ResolutionUnit = resolutionUnit;
this.HorizontalResolution = horizontalResolution;
this.VerticalResolution = verticalResolution;
}
public ushort ResolutionUnit { get; }
public double? HorizontalResolution { get; }
public double? VerticalResolution { get; }
}
}

4
src/ImageSharp/Common/Helpers/InliningOptions.cs

@ -12,6 +12,10 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
internal static class InliningOptions internal static class InliningOptions
{ {
/// <summary>
/// <see cref="MethodImplOptions.AggressiveInlining"/> regardless of the build conditions.
/// </summary>
public const MethodImplOptions AlwaysInline = MethodImplOptions.AggressiveInlining;
#if PROFILING #if PROFILING
public const MethodImplOptions HotPath = MethodImplOptions.NoInlining; public const MethodImplOptions HotPath = MethodImplOptions.NoInlining;
public const MethodImplOptions ShortMethod = MethodImplOptions.NoInlining; public const MethodImplOptions ShortMethod = MethodImplOptions.NoInlining;

139
src/ImageSharp/Common/Helpers/Numerics.cs

@ -23,6 +23,16 @@ namespace SixLabors.ImageSharp
private const int ShuffleAlphaControl = 0b_11_11_11_11; private const int ShuffleAlphaControl = 0b_11_11_11_11;
#endif #endif
#if !SUPPORTS_BITOPERATIONS
private static ReadOnlySpan<byte> Log2DeBruijn => new byte[32]
{
00, 09, 01, 10, 13, 21, 02, 29,
11, 14, 16, 18, 22, 25, 03, 30,
08, 12, 20, 28, 15, 17, 24, 07,
19, 27, 23, 06, 26, 05, 04, 31
};
#endif
/// <summary> /// <summary>
/// Determine the Greatest CommonDivisor (GCD) of two numbers. /// Determine the Greatest CommonDivisor (GCD) of two numbers.
/// </summary> /// </summary>
@ -748,5 +758,134 @@ namespace SixLabors.ImageSharp
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Lerp(float value1, float value2, float amount) public static float Lerp(float value1, float value2, float amount)
=> ((value2 - value1) * amount) + value1; => ((value2 - value1) * amount) + value1;
#if SUPPORTS_RUNTIME_INTRINSICS
/// <summary>
/// Accumulates 8-bit integers into <paramref name="accumulator"/> by
/// widening them to 32-bit integers and performing four additions.
/// </summary>
/// <remarks>
/// <c>byte(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)</c>
/// is widened and added onto <paramref name="accumulator"/> as such:
/// <code>
/// accumulator += i32(1, 2, 3, 4);
/// accumulator += i32(5, 6, 7, 8);
/// accumulator += i32(9, 10, 11, 12);
/// accumulator += i32(13, 14, 15, 16);
/// </code>
/// </remarks>
/// <param name="accumulator">The accumulator destination.</param>
/// <param name="values">The values to accumulate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Accumulate(ref Vector<uint> accumulator, Vector<byte> values)
{
Vector.Widen(values, out Vector<ushort> shortLow, out Vector<ushort> shortHigh);
Vector.Widen(shortLow, out Vector<uint> intLow, out Vector<uint> intHigh);
accumulator += intLow;
accumulator += intHigh;
Vector.Widen(shortHigh, out intLow, out intHigh);
accumulator += intLow;
accumulator += intHigh;
}
/// <summary>
/// Reduces elements of the vector into one sum.
/// </summary>
/// <param name="accumulator">The accumulator to reduce.</param>
/// <returns>The sum of all elements.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ReduceSum(Vector128<int> accumulator)
{
if (Ssse3.IsSupported)
{
Vector128<int> hadd = Ssse3.HorizontalAdd(accumulator, accumulator);
Vector128<int> swapped = Sse2.Shuffle(hadd, 0x1);
Vector128<int> tmp = Sse2.Add(hadd, swapped);
// Vector128<int>.ToScalar() isn't optimized pre-net5.0 https://github.com/dotnet/runtime/pull/37882
return Sse2.ConvertToInt32(tmp);
}
else
{
int sum = 0;
for (int i = 0; i < Vector128<int>.Count; i++)
{
sum += accumulator.GetElement(i);
}
return sum;
}
}
/// <summary>
/// Reduces even elements of the vector into one sum.
/// </summary>
/// <param name="accumulator">The accumulator to reduce.</param>
/// <returns>The sum of even elements.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int EvenReduceSum(Vector256<int> accumulator)
{
Vector128<int> vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper()); // add upper lane to lower lane
vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10)); // add high to low
// Vector128<int>.ToScalar() isn't optimized pre-net5.0 https://github.com/dotnet/runtime/pull/37882
return Sse2.ConvertToInt32(vsum);
}
#endif
/// <summary>
/// Calculates floored log of the specified value, base 2.
/// Note that by convention, input value 0 returns 0 since Log(0) is undefined.
/// </summary>
/// <param name="value">The value.</param>
public static int Log2(uint value)
{
#if SUPPORTS_BITOPERATIONS
return BitOperations.Log2(value);
#else
return Log2SoftwareFallback(value);
#endif
}
#if !SUPPORTS_BITOPERATIONS
/// <summary>
/// Calculates floored log of the specified value, base 2.
/// Note that by convention, input value 0 returns 0 since Log(0) is undefined.
/// Bit hacking with deBruijn sequence, extremely fast yet does not use any intrinsics so will work on every platform/runtime.
/// </summary>
/// <remarks>
/// Description of this bit hacking can be found here:
/// https://cstheory.stackexchange.com/questions/19524/using-the-de-bruijn-sequence-to-find-the-lceil-log-2-v-rceil-of-an-integer
/// </remarks>
/// <param name="value">The value.</param>
private static int Log2SoftwareFallback(uint value)
{
// No AggressiveInlining due to large method size
// Has conventional contract 0->0 (Log(0) is undefined) by default, no need for if checking
// Fill trailing zeros with ones, eg 00010010 becomes 00011111
value |= value >> 01;
value |= value >> 02;
value |= value >> 04;
value |= value >> 08;
value |= value >> 16;
// uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check
return Unsafe.AddByteOffset(
ref MemoryMarshal.GetReference(Log2DeBruijn),
(IntPtr)(int)((value * 0x07C4ACDDu) >> 27)); // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here
}
#endif
/// <summary>
/// Fast division with ceiling for <see cref="uint"/> numbers.
/// </summary>
/// <param name="value">Divident value.</param>
/// <param name="divisor">Divisor value.</param>
/// <returns>Ceiled division result.</returns>
public static uint DivideCeil(uint value, uint divisor) => (value + divisor - 1) / divisor;
} }
} }

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);
}
}

170
src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs

@ -532,11 +532,12 @@ namespace SixLabors.ImageSharp
/// <summary> /// <summary>
/// Performs a multiplication and an addition of the <see cref="Vector256{T}"/>. /// Performs a multiplication and an addition of the <see cref="Vector256{T}"/>.
/// </summary> /// </summary>
/// <remarks>ret = (vm0 * vm1) + va</remarks>
/// <param name="va">The vector to add to the intermediate result.</param> /// <param name="va">The vector to add to the intermediate result.</param>
/// <param name="vm0">The first vector to multiply.</param> /// <param name="vm0">The first vector to multiply.</param>
/// <param name="vm1">The second vector to multiply.</param> /// <param name="vm1">The second vector to multiply.</param>
/// <returns>The <see cref="Vector256{T}"/>.</returns> /// <returns>The <see cref="Vector256{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.AlwaysInline)]
public static Vector256<float> MultiplyAdd( public static Vector256<float> MultiplyAdd(
in Vector256<float> va, in Vector256<float> va,
in Vector256<float> vm0, in Vector256<float> vm0,
@ -552,6 +553,30 @@ namespace SixLabors.ImageSharp
} }
} }
/// <summary>
/// Performs a multiplication and a substraction of the <see cref="Vector256{T}"/>.
/// </summary>
/// <remarks>ret = (vm0 * vm1) - vs</remarks>
/// <param name="vs">The vector to substract from the intermediate result.</param>
/// <param name="vm0">The first vector to multiply.</param>
/// <param name="vm1">The second vector to multiply.</param>
/// <returns>The <see cref="Vector256{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Vector256<float> MultiplySubstract(
in Vector256<float> vs,
in Vector256<float> vm0,
in Vector256<float> vm1)
{
if (Fma.IsSupported)
{
return Fma.MultiplySubtract(vm1, vm0, vs);
}
else
{
return Avx.Subtract(Avx.Multiply(vm0, vm1), vs);
}
}
/// <summary> /// <summary>
/// <see cref="ByteToNormalizedFloat"/> as many elements as possible, slicing them down (keeping the remainder). /// <see cref="ByteToNormalizedFloat"/> as many elements as possible, slicing them down (keeping the remainder).
/// </summary> /// </summary>
@ -597,90 +622,89 @@ namespace SixLabors.ImageSharp
ReadOnlySpan<byte> source, ReadOnlySpan<byte> source,
Span<float> dest) Span<float> dest)
{ {
if (Avx2.IsSupported) fixed (byte* sourceBase = source)
{ {
VerifySpanInput(source, dest, Vector256<byte>.Count); if (Avx2.IsSupported)
{
VerifySpanInput(source, dest, Vector256<byte>.Count);
int n = dest.Length / Vector256<byte>.Count; int n = dest.Length / Vector256<byte>.Count;
byte* sourceBase = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(source)); ref Vector256<float> destBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(dest));
ref Vector256<float> destBase = var scale = Vector256.Create(1 / (float)byte.MaxValue);
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(dest));
var scale = Vector256.Create(1 / (float)byte.MaxValue); for (int i = 0; i < n; i++)
{
for (int i = 0; i < n; i++) int si = Vector256<byte>.Count * i;
{ Vector256<int> i0 = Avx2.ConvertToVector256Int32(sourceBase + si);
int si = Vector256<byte>.Count * i; Vector256<int> i1 = Avx2.ConvertToVector256Int32(sourceBase + si + Vector256<int>.Count);
Vector256<int> i0 = Avx2.ConvertToVector256Int32(sourceBase + si); Vector256<int> i2 = Avx2.ConvertToVector256Int32(sourceBase + si + (Vector256<int>.Count * 2));
Vector256<int> i1 = Avx2.ConvertToVector256Int32(sourceBase + si + Vector256<int>.Count); Vector256<int> i3 = Avx2.ConvertToVector256Int32(sourceBase + si + (Vector256<int>.Count * 3));
Vector256<int> i2 = Avx2.ConvertToVector256Int32(sourceBase + si + (Vector256<int>.Count * 2));
Vector256<int> i3 = Avx2.ConvertToVector256Int32(sourceBase + si + (Vector256<int>.Count * 3)); Vector256<float> f0 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i0));
Vector256<float> f1 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i1));
Vector256<float> f0 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i0)); Vector256<float> f2 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i2));
Vector256<float> f1 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i1)); Vector256<float> f3 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i3));
Vector256<float> f2 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i2));
Vector256<float> f3 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i3)); ref Vector256<float> d = ref Unsafe.Add(ref destBase, i * 4);
ref Vector256<float> d = ref Unsafe.Add(ref destBase, i * 4); d = f0;
Unsafe.Add(ref d, 1) = f1;
d = f0; Unsafe.Add(ref d, 2) = f2;
Unsafe.Add(ref d, 1) = f1; Unsafe.Add(ref d, 3) = f3;
Unsafe.Add(ref d, 2) = f2; }
Unsafe.Add(ref d, 3) = f3;
} }
} else
else {
{ // Sse
// Sse VerifySpanInput(source, dest, Vector128<byte>.Count);
VerifySpanInput(source, dest, Vector128<byte>.Count);
int n = dest.Length / Vector128<byte>.Count;
byte* sourceBase = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(source));
ref Vector128<float> destBase = int n = dest.Length / Vector128<byte>.Count;
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(dest));
var scale = Vector128.Create(1 / (float)byte.MaxValue); ref Vector128<float> destBase =
Vector128<byte> zero = Vector128<byte>.Zero; ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(dest));
for (int i = 0; i < n; i++) var scale = Vector128.Create(1 / (float)byte.MaxValue);
{ Vector128<byte> zero = Vector128<byte>.Zero;
int si = Vector128<byte>.Count * i;
Vector128<int> i0, i1, i2, i3; for (int i = 0; i < n; i++)
if (Sse41.IsSupported)
{
i0 = Sse41.ConvertToVector128Int32(sourceBase + si);
i1 = Sse41.ConvertToVector128Int32(sourceBase + si + Vector128<int>.Count);
i2 = Sse41.ConvertToVector128Int32(sourceBase + si + (Vector128<int>.Count * 2));
i3 = Sse41.ConvertToVector128Int32(sourceBase + si + (Vector128<int>.Count * 3));
}
else
{ {
Vector128<byte> b = Sse2.LoadVector128(sourceBase + si); int si = Vector128<byte>.Count * i;
Vector128<short> s0 = Sse2.UnpackLow(b, zero).AsInt16();
Vector128<short> s1 = Sse2.UnpackHigh(b, zero).AsInt16(); Vector128<int> i0, i1, i2, i3;
if (Sse41.IsSupported)
i0 = Sse2.UnpackLow(s0, zero.AsInt16()).AsInt32(); {
i1 = Sse2.UnpackHigh(s0, zero.AsInt16()).AsInt32(); i0 = Sse41.ConvertToVector128Int32(sourceBase + si);
i2 = Sse2.UnpackLow(s1, zero.AsInt16()).AsInt32(); i1 = Sse41.ConvertToVector128Int32(sourceBase + si + Vector128<int>.Count);
i3 = Sse2.UnpackHigh(s1, zero.AsInt16()).AsInt32(); i2 = Sse41.ConvertToVector128Int32(sourceBase + si + (Vector128<int>.Count * 2));
i3 = Sse41.ConvertToVector128Int32(sourceBase + si + (Vector128<int>.Count * 3));
}
else
{
Vector128<byte> b = Sse2.LoadVector128(sourceBase + si);
Vector128<short> s0 = Sse2.UnpackLow(b, zero).AsInt16();
Vector128<short> s1 = Sse2.UnpackHigh(b, zero).AsInt16();
i0 = Sse2.UnpackLow(s0, zero.AsInt16()).AsInt32();
i1 = Sse2.UnpackHigh(s0, zero.AsInt16()).AsInt32();
i2 = Sse2.UnpackLow(s1, zero.AsInt16()).AsInt32();
i3 = Sse2.UnpackHigh(s1, zero.AsInt16()).AsInt32();
}
Vector128<float> f0 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i0));
Vector128<float> f1 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i1));
Vector128<float> f2 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i2));
Vector128<float> f3 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i3));
ref Vector128<float> d = ref Unsafe.Add(ref destBase, i * 4);
d = f0;
Unsafe.Add(ref d, 1) = f1;
Unsafe.Add(ref d, 2) = f2;
Unsafe.Add(ref d, 3) = f3;
} }
Vector128<float> f0 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i0));
Vector128<float> f1 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i1));
Vector128<float> f2 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i2));
Vector128<float> f3 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i3));
ref Vector128<float> d = ref Unsafe.Add(ref destBase, i * 4);
d = f0;
Unsafe.Add(ref d, 1) = f1;
Unsafe.Add(ref d, 2) = f2;
Unsafe.Add(ref d, 3) = f3;
} }
} }
} }

2
src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs

@ -5,9 +5,7 @@ using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86; using System.Runtime.Intrinsics.X86;
#endif #endif

45
src/ImageSharp/Common/Helpers/UnitConverter.cs

@ -30,6 +30,11 @@ namespace SixLabors.ImageSharp.Common.Helpers
/// </summary> /// </summary>
private const double InchesInMeter = 1 / 0.0254D; private const double InchesInMeter = 1 / 0.0254D;
/// <summary>
/// The default resolution unit value.
/// </summary>
private const PixelResolutionUnit DefaultResolutionUnit = PixelResolutionUnit.PixelsPerInch;
/// <summary> /// <summary>
/// Scales the value from centimeters to meters. /// Scales the value from centimeters to meters.
/// </summary> /// </summary>
@ -89,7 +94,45 @@ namespace SixLabors.ImageSharp.Common.Helpers
IExifValue<ushort> resolution = profile.GetValue(ExifTag.ResolutionUnit); IExifValue<ushort> resolution = profile.GetValue(ExifTag.ResolutionUnit);
// EXIF is 1, 2, 3 so we minus "1" off the result. // EXIF is 1, 2, 3 so we minus "1" off the result.
return resolution is null ? default : (PixelResolutionUnit)(byte)(resolution.Value - 1); return resolution is null ? DefaultResolutionUnit : (PixelResolutionUnit)(byte)(resolution.Value - 1);
}
/// <summary>
/// Gets the exif profile resolution values.
/// </summary>
/// <param name="unit">The resolution unit.</param>
/// <param name="horizontal">The horizontal resolution value.</param>
/// <param name="vertical">The vertical resolution value.</param>
/// <returns><see cref="ExifResolutionValues"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static ExifResolutionValues GetExifResolutionValues(PixelResolutionUnit unit, double horizontal, double vertical)
{
switch (unit)
{
case PixelResolutionUnit.AspectRatio:
case PixelResolutionUnit.PixelsPerInch:
case PixelResolutionUnit.PixelsPerCentimeter:
break;
case PixelResolutionUnit.PixelsPerMeter:
{
unit = PixelResolutionUnit.PixelsPerCentimeter;
horizontal = MeterToCm(horizontal);
vertical = MeterToCm(vertical);
}
break;
default:
unit = PixelResolutionUnit.PixelsPerInch;
break;
}
ushort exifUnit = (ushort)(unit + 1);
if (unit == PixelResolutionUnit.AspectRatio)
{
return new ExifResolutionValues(exifUnit, null, null);
}
return new ExifResolutionValues(exifUnit, horizontal, vertical);
} }
} }
} }

172
src/ImageSharp/Formats/Png/Zlib/Adler32.cs → src/ImageSharp/Compression/Zlib/Adler32.cs

@ -9,7 +9,7 @@ using System.Runtime.Intrinsics.X86;
#endif #endif
#pragma warning disable IDE0007 // Use implicit type #pragma warning disable IDE0007 // Use implicit type
namespace SixLabors.ImageSharp.Formats.Png.Zlib namespace SixLabors.ImageSharp.Compression.Zlib
{ {
/// <summary> /// <summary>
/// Calculates the 32 bit Adler checksum of a given buffer according to /// Calculates the 32 bit Adler checksum of a given buffer according to
@ -91,115 +91,117 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
int index = 0; int index = 0;
fixed (byte* bufferPtr = buffer) fixed (byte* bufferPtr = buffer)
fixed (byte* tapPtr = Tap1Tap2)
{ {
index += (int)blocks * BLOCK_SIZE; fixed (byte* tapPtr = Tap1Tap2)
var localBufferPtr = bufferPtr;
// _mm_setr_epi8 on x86
Vector128<sbyte> tap1 = Sse2.LoadVector128((sbyte*)tapPtr);
Vector128<sbyte> tap2 = Sse2.LoadVector128((sbyte*)(tapPtr + 0x10));
Vector128<byte> zero = Vector128<byte>.Zero;
var ones = Vector128.Create((short)1);
while (blocks > 0)
{ {
uint n = NMAX / BLOCK_SIZE; /* The NMAX constraint. */ index += (int)blocks * BLOCK_SIZE;
if (n > blocks) var localBufferPtr = bufferPtr;
{
n = blocks;
}
blocks -= n; // _mm_setr_epi8 on x86
Vector128<sbyte> tap1 = Sse2.LoadVector128((sbyte*)tapPtr);
Vector128<sbyte> tap2 = Sse2.LoadVector128((sbyte*)(tapPtr + 0x10));
Vector128<byte> zero = Vector128<byte>.Zero;
var ones = Vector128.Create((short)1);
// Process n blocks of data. At most NMAX data bytes can be while (blocks > 0)
// processed before s2 must be reduced modulo BASE.
Vector128<uint> v_ps = Vector128.CreateScalar(s1 * n);
Vector128<uint> v_s2 = Vector128.CreateScalar(s2);
Vector128<uint> v_s1 = Vector128<uint>.Zero;
do
{ {
// Load 32 input bytes. uint n = NMAX / BLOCK_SIZE; /* The NMAX constraint. */
Vector128<byte> bytes1 = Sse3.LoadDquVector128(localBufferPtr); if (n > blocks)
Vector128<byte> bytes2 = Sse3.LoadDquVector128(localBufferPtr + 0x10); {
n = blocks;
}
// Add previous block byte sum to v_ps. blocks -= n;
v_ps = Sse2.Add(v_ps, v_s1);
// Horizontally add the bytes for s1, multiply-adds the // Process n blocks of data. At most NMAX data bytes can be
// bytes by [ 32, 31, 30, ... ] for s2. // processed before s2 must be reduced modulo BASE.
v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes1, zero).AsUInt32()); Vector128<uint> v_ps = Vector128.CreateScalar(s1 * n);
Vector128<short> mad1 = Ssse3.MultiplyAddAdjacent(bytes1, tap1); Vector128<uint> v_s2 = Vector128.CreateScalar(s2);
v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad1, ones).AsUInt32()); Vector128<uint> v_s1 = Vector128<uint>.Zero;
v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes2, zero).AsUInt32()); do
Vector128<short> mad2 = Ssse3.MultiplyAddAdjacent(bytes2, tap2); {
v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2, ones).AsUInt32()); // Load 32 input bytes.
Vector128<byte> bytes1 = Sse3.LoadDquVector128(localBufferPtr);
Vector128<byte> bytes2 = Sse3.LoadDquVector128(localBufferPtr + 0x10);
localBufferPtr += BLOCK_SIZE; // Add previous block byte sum to v_ps.
} v_ps = Sse2.Add(v_ps, v_s1);
while (--n > 0);
v_s2 = Sse2.Add(v_s2, Sse2.ShiftLeftLogical(v_ps, 5)); // Horizontally add the bytes for s1, multiply-adds the
// bytes by [ 32, 31, 30, ... ] for s2.
v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes1, zero).AsUInt32());
Vector128<short> mad1 = Ssse3.MultiplyAddAdjacent(bytes1, tap1);
v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad1, ones).AsUInt32());
// Sum epi32 ints v_s1(s2) and accumulate in s1(s2). v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes2, zero).AsUInt32());
const byte S2301 = 0b1011_0001; // A B C D -> B A D C Vector128<short> mad2 = Ssse3.MultiplyAddAdjacent(bytes2, tap2);
const byte S1032 = 0b0100_1110; // A B C D -> C D A B v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2, ones).AsUInt32());
v_s1 = Sse2.Add(v_s1, Sse2.Shuffle(v_s1, S1032)); localBufferPtr += BLOCK_SIZE;
}
while (--n > 0);
s1 += v_s1.ToScalar(); v_s2 = Sse2.Add(v_s2, Sse2.ShiftLeftLogical(v_ps, 5));
v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, S2301)); // Sum epi32 ints v_s1(s2) and accumulate in s1(s2).
v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, S1032)); const byte S2301 = 0b1011_0001; // A B C D -> B A D C
const byte S1032 = 0b0100_1110; // A B C D -> C D A B
s2 = v_s2.ToScalar(); v_s1 = Sse2.Add(v_s1, Sse2.Shuffle(v_s1, S1032));
// Reduce. s1 += v_s1.ToScalar();
s1 %= BASE;
s2 %= BASE;
}
if (length > 0) v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, S2301));
{ v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, S1032));
if (length >= 16)
{
s2 += s1 += localBufferPtr[0];
s2 += s1 += localBufferPtr[1];
s2 += s1 += localBufferPtr[2];
s2 += s1 += localBufferPtr[3];
s2 += s1 += localBufferPtr[4];
s2 += s1 += localBufferPtr[5];
s2 += s1 += localBufferPtr[6];
s2 += s1 += localBufferPtr[7];
s2 += s1 += localBufferPtr[8];
s2 += s1 += localBufferPtr[9];
s2 += s1 += localBufferPtr[10];
s2 += s1 += localBufferPtr[11];
s2 += s1 += localBufferPtr[12];
s2 += s1 += localBufferPtr[13];
s2 += s1 += localBufferPtr[14];
s2 += s1 += localBufferPtr[15];
localBufferPtr += 16; s2 = v_s2.ToScalar();
length -= 16;
}
while (length-- > 0) // Reduce.
{ s1 %= BASE;
s2 += s1 += *localBufferPtr++; s2 %= BASE;
} }
if (s1 >= BASE) if (length > 0)
{ {
s1 -= BASE; if (length >= 16)
{
s2 += s1 += localBufferPtr[0];
s2 += s1 += localBufferPtr[1];
s2 += s1 += localBufferPtr[2];
s2 += s1 += localBufferPtr[3];
s2 += s1 += localBufferPtr[4];
s2 += s1 += localBufferPtr[5];
s2 += s1 += localBufferPtr[6];
s2 += s1 += localBufferPtr[7];
s2 += s1 += localBufferPtr[8];
s2 += s1 += localBufferPtr[9];
s2 += s1 += localBufferPtr[10];
s2 += s1 += localBufferPtr[11];
s2 += s1 += localBufferPtr[12];
s2 += s1 += localBufferPtr[13];
s2 += s1 += localBufferPtr[14];
s2 += s1 += localBufferPtr[15];
localBufferPtr += 16;
length -= 16;
}
while (length-- > 0)
{
s2 += s1 += *localBufferPtr++;
}
if (s1 >= BASE)
{
s1 -= BASE;
}
s2 %= BASE;
} }
s2 %= BASE; return s1 | (s2 << 16);
} }
return s1 | (s2 << 16);
} }
} }
#endif #endif

2
src/ImageSharp/Formats/Png/Zlib/Crc32.Lut.cs → src/ImageSharp/Compression/Zlib/Crc32.Lut.cs

@ -1,7 +1,7 @@
// 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.Png.Zlib namespace SixLabors.ImageSharp.Compression.Zlib
{ {
/// <content> /// <content>
/// Contains precalulated tables for scalar calculations. /// Contains precalulated tables for scalar calculations.

217
src/ImageSharp/Compression/Zlib/Crc32.cs

@ -0,0 +1,217 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
namespace SixLabors.ImageSharp.Compression.Zlib
{
/// <summary>
/// Calculates the 32 bit Cyclic Redundancy Check (CRC) checksum of a given buffer
/// according to the IEEE 802.3 specification.
/// </summary>
internal static partial class Crc32
{
/// <summary>
/// The default initial seed value of a Crc32 checksum calculation.
/// </summary>
public const uint SeedValue = 0U;
#if SUPPORTS_RUNTIME_INTRINSICS
private const int MinBufferSize = 64;
private const int ChunksizeMask = 15;
// Definitions of the bit-reflected domain constants k1, k2, k3, etc and
// the CRC32+Barrett polynomials given at the end of the paper.
private static readonly ulong[] K05Poly =
{
0x0154442bd4, 0x01c6e41596, // k1, k2
0x01751997d0, 0x00ccaa009e, // k3, k4
0x0163cd6124, 0x0000000000, // k5, k0
0x01db710641, 0x01f7011641 // polynomial
};
#endif
/// <summary>
/// Calculates the CRC checksum with the bytes taken from the span.
/// </summary>
/// <param name="buffer">The readonly span of bytes.</param>
/// <returns>The <see cref="uint"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static uint Calculate(ReadOnlySpan<byte> buffer)
=> Calculate(SeedValue, buffer);
/// <summary>
/// Calculates the CRC checksum with the bytes taken from the span and seed.
/// </summary>
/// <param name="crc">The input CRC value.</param>
/// <param name="buffer">The readonly span of bytes.</param>
/// <returns>The <see cref="uint"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static uint Calculate(uint crc, ReadOnlySpan<byte> buffer)
{
if (buffer.IsEmpty)
{
return crc;
}
#if SUPPORTS_RUNTIME_INTRINSICS
if (Sse41.IsSupported && Pclmulqdq.IsSupported && buffer.Length >= MinBufferSize)
{
return ~CalculateSse(~crc, buffer);
}
else
{
return ~CalculateScalar(~crc, buffer);
}
#else
return ~CalculateScalar(~crc, buffer);
#endif
}
#if SUPPORTS_RUNTIME_INTRINSICS
// Based on https://github.com/chromium/chromium/blob/master/third_party/zlib/crc32_simd.c
[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
private static unsafe uint CalculateSse(uint crc, ReadOnlySpan<byte> buffer)
{
int chunksize = buffer.Length & ~ChunksizeMask;
int length = chunksize;
fixed (byte* bufferPtr = buffer)
{
fixed (ulong* k05PolyPtr = K05Poly)
{
byte* localBufferPtr = bufferPtr;
ulong* localK05PolyPtr = k05PolyPtr;
// There's at least one block of 64.
Vector128<ulong> x1 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x00));
Vector128<ulong> x2 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x10));
Vector128<ulong> x3 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x20));
Vector128<ulong> x4 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x30));
Vector128<ulong> x5;
x1 = Sse2.Xor(x1, Sse2.ConvertScalarToVector128UInt32(crc).AsUInt64());
// k1, k2
Vector128<ulong> x0 = Sse2.LoadVector128(localK05PolyPtr + 0x0);
localBufferPtr += 64;
length -= 64;
// Parallel fold blocks of 64, if any.
while (length >= 64)
{
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
Vector128<ulong> x6 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x00);
Vector128<ulong> x7 = Pclmulqdq.CarrylessMultiply(x3, x0, 0x00);
Vector128<ulong> x8 = Pclmulqdq.CarrylessMultiply(x4, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x11);
x3 = Pclmulqdq.CarrylessMultiply(x3, x0, 0x11);
x4 = Pclmulqdq.CarrylessMultiply(x4, x0, 0x11);
Vector128<ulong> y5 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x00));
Vector128<ulong> y6 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x10));
Vector128<ulong> y7 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x20));
Vector128<ulong> y8 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x30));
x1 = Sse2.Xor(x1, x5);
x2 = Sse2.Xor(x2, x6);
x3 = Sse2.Xor(x3, x7);
x4 = Sse2.Xor(x4, x8);
x1 = Sse2.Xor(x1, y5);
x2 = Sse2.Xor(x2, y6);
x3 = Sse2.Xor(x3, y7);
x4 = Sse2.Xor(x4, y8);
localBufferPtr += 64;
length -= 64;
}
// Fold into 128-bits.
// k3, k4
x0 = Sse2.LoadVector128(k05PolyPtr + 0x2);
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x1 = Sse2.Xor(x1, x2);
x1 = Sse2.Xor(x1, x5);
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x1 = Sse2.Xor(x1, x3);
x1 = Sse2.Xor(x1, x5);
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x1 = Sse2.Xor(x1, x4);
x1 = Sse2.Xor(x1, x5);
// Single fold blocks of 16, if any.
while (length >= 16)
{
x2 = Sse2.LoadVector128((ulong*)localBufferPtr);
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x1 = Sse2.Xor(x1, x2);
x1 = Sse2.Xor(x1, x5);
localBufferPtr += 16;
length -= 16;
}
// Fold 128 - bits to 64 - bits.
x2 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x10);
x3 = Vector128.Create(~0, 0, ~0, 0).AsUInt64(); // _mm_setr_epi32 on x86
x1 = Sse2.ShiftRightLogical128BitLane(x1, 8);
x1 = Sse2.Xor(x1, x2);
// k5, k0
x0 = Sse2.LoadScalarVector128(localK05PolyPtr + 0x4);
x2 = Sse2.ShiftRightLogical128BitLane(x1, 4);
x1 = Sse2.And(x1, x3);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Sse2.Xor(x1, x2);
// Barret reduce to 32-bits.
// polynomial
x0 = Sse2.LoadVector128(localK05PolyPtr + 0x6);
x2 = Sse2.And(x1, x3);
x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x10);
x2 = Sse2.And(x2, x3);
x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x00);
x1 = Sse2.Xor(x1, x2);
crc = (uint)Sse41.Extract(x1.AsInt32(), 1);
return buffer.Length - chunksize == 0 ? crc : CalculateScalar(crc, buffer.Slice(chunksize));
}
}
}
#endif
[MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
private static uint CalculateScalar(uint crc, ReadOnlySpan<byte> buffer)
{
ref uint crcTableRef = ref MemoryMarshal.GetReference(CrcTable.AsSpan());
ref byte bufferRef = ref MemoryMarshal.GetReference(buffer);
for (int i = 0; i < buffer.Length; i++)
{
crc = Unsafe.Add(ref crcTableRef, (int)((crc ^ Unsafe.Add(ref bufferRef, i)) & 0xFF)) ^ (crc >> 8);
}
return crc;
}
}
}

81
src/ImageSharp/Compression/Zlib/DeflateCompressionLevel.cs

@ -0,0 +1,81 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Compression.Zlib
{
/// <summary>
/// Provides enumeration of available deflate compression levels.
/// </summary>
public enum DeflateCompressionLevel
{
/// <summary>
/// Level 0. Equivalent to <see cref="NoCompression"/>.
/// </summary>
Level0 = 0,
/// <summary>
/// No compression. Equivalent to <see cref="Level0"/>.
/// </summary>
NoCompression = Level0,
/// <summary>
/// Level 1. Equivalent to <see cref="BestSpeed"/>.
/// </summary>
Level1 = 1,
/// <summary>
/// Best speed compression level.
/// </summary>
BestSpeed = Level1,
/// <summary>
/// Level 2.
/// </summary>
Level2 = 2,
/// <summary>
/// Level 3.
/// </summary>
Level3 = 3,
/// <summary>
/// Level 4.
/// </summary>
Level4 = 4,
/// <summary>
/// Level 5.
/// </summary>
Level5 = 5,
/// <summary>
/// Level 6. Equivalent to <see cref="DefaultCompression"/>.
/// </summary>
Level6 = 6,
/// <summary>
/// The default compression level. Equivalent to <see cref="Level6"/>.
/// </summary>
DefaultCompression = Level6,
/// <summary>
/// Level 7.
/// </summary>
Level7 = 7,
/// <summary>
/// Level 8.
/// </summary>
Level8 = 8,
/// <summary>
/// Level 9. Equivalent to <see cref="BestCompression"/>.
/// </summary>
Level9 = 9,
/// <summary>
/// Best compression level. Equivalent to <see cref="Level9"/>.
/// </summary>
BestCompression = Level9,
}
}

2
src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs → src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs

@ -4,7 +4,7 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Png.Zlib namespace SixLabors.ImageSharp.Compression.Zlib
{ {
internal static class DeflateThrowHelper internal static class DeflateThrowHelper
{ {

4
src/ImageSharp/Formats/Png/Zlib/Deflater.cs → src/ImageSharp/Compression/Zlib/Deflater.cs

@ -5,7 +5,7 @@ using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib namespace SixLabors.ImageSharp.Compression.Zlib
{ {
/// <summary> /// <summary>
/// This class compresses input with the deflate algorithm described in RFC 1951. /// This class compresses input with the deflate algorithm described in RFC 1951.
@ -222,7 +222,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// The number of compressed bytes added to the output, or 0 if either /// The number of compressed bytes added to the output, or 0 if either
/// <see cref="IsNeedingInput"/> or <see cref="IsFinished"/> returns true or length is zero. /// <see cref="IsNeedingInput"/> or <see cref="IsFinished"/> returns true or length is zero.
/// </returns> /// </returns>
public int Deflate(byte[] output, int offset, int length) public int Deflate(Span<byte> output, int offset, int length)
{ {
int origLength = length; int origLength = length;

2
src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs → src/ImageSharp/Compression/Zlib/DeflaterConstants.cs

@ -4,7 +4,7 @@
// <auto-generated/> // <auto-generated/>
using System; using System;
namespace SixLabors.ImageSharp.Formats.Png.Zlib namespace SixLabors.ImageSharp.Compression.Zlib
{ {
/// <summary> /// <summary>
/// This class contains constants used for deflation. /// This class contains constants used for deflation.

43
src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs → src/ImageSharp/Compression/Zlib/DeflaterEngine.cs

@ -6,7 +6,7 @@ using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib namespace SixLabors.ImageSharp.Compression.Zlib
{ {
/// <summary> /// <summary>
/// Strategies for deflater /// Strategies for deflater
@ -130,9 +130,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// This array contains the part of the uncompressed stream that /// This array contains the part of the uncompressed stream that
/// is of relevance. The current character is indexed by strstart. /// is of relevance. The current character is indexed by strstart.
/// </summary> /// </summary>
private IManagedByteBuffer windowMemoryOwner; private IMemoryOwner<byte> windowMemoryOwner;
private MemoryHandle windowMemoryHandle; private MemoryHandle windowMemoryHandle;
private readonly byte[] window; private readonly Memory<byte> window;
private readonly byte* pinnedWindowPointer; private readonly byte* pinnedWindowPointer;
private int maxChain; private int maxChain;
@ -153,19 +153,19 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
// Create pinned pointers to the various buffers to allow indexing // Create pinned pointers to the various buffers to allow indexing
// without bounds checks. // without bounds checks.
this.windowMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(2 * DeflaterConstants.WSIZE); this.windowMemoryOwner = memoryAllocator.Allocate<byte>(2 * DeflaterConstants.WSIZE);
this.window = this.windowMemoryOwner.Array; this.window = this.windowMemoryOwner.Memory;
this.windowMemoryHandle = this.windowMemoryOwner.Memory.Pin(); this.windowMemoryHandle = this.window.Pin();
this.pinnedWindowPointer = (byte*)this.windowMemoryHandle.Pointer; this.pinnedWindowPointer = (byte*)this.windowMemoryHandle.Pointer;
this.headMemoryOwner = memoryAllocator.Allocate<short>(DeflaterConstants.HASH_SIZE); this.headMemoryOwner = memoryAllocator.Allocate<short>(DeflaterConstants.HASH_SIZE);
this.head = this.headMemoryOwner.Memory; this.head = this.headMemoryOwner.Memory;
this.headMemoryHandle = this.headMemoryOwner.Memory.Pin(); this.headMemoryHandle = this.head.Pin();
this.pinnedHeadPointer = (short*)this.headMemoryHandle.Pointer; this.pinnedHeadPointer = (short*)this.headMemoryHandle.Pointer;
this.prevMemoryOwner = memoryAllocator.Allocate<short>(DeflaterConstants.WSIZE); this.prevMemoryOwner = memoryAllocator.Allocate<short>(DeflaterConstants.WSIZE);
this.prev = this.prevMemoryOwner.Memory; this.prev = this.prevMemoryOwner.Memory;
this.prevMemoryHandle = this.prevMemoryOwner.Memory.Pin(); this.prevMemoryHandle = this.prev.Pin();
this.pinnedPrevPointer = (short*)this.prevMemoryHandle.Pointer; this.pinnedPrevPointer = (short*)this.prevMemoryHandle.Pointer;
// We start at index 1, to avoid an implementation deficiency, that // We start at index 1, to avoid an implementation deficiency, that
@ -303,7 +303,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
case DeflaterConstants.DEFLATE_STORED: case DeflaterConstants.DEFLATE_STORED:
if (this.strstart > this.blockStart) if (this.strstart > this.blockStart)
{ {
this.huffman.FlushStoredBlock(this.window, this.blockStart, this.strstart - this.blockStart, false); this.huffman.FlushStoredBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false);
this.blockStart = this.strstart; this.blockStart = this.strstart;
} }
@ -313,7 +313,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
case DeflaterConstants.DEFLATE_FAST: case DeflaterConstants.DEFLATE_FAST:
if (this.strstart > this.blockStart) if (this.strstart > this.blockStart)
{ {
this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, false); this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false);
this.blockStart = this.strstart; this.blockStart = this.strstart;
} }
@ -327,7 +327,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
if (this.strstart > this.blockStart) if (this.strstart > this.blockStart)
{ {
this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, false); this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false);
this.blockStart = this.strstart; this.blockStart = this.strstart;
} }
@ -362,7 +362,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
more = this.inputEnd - this.inputOff; more = this.inputEnd - this.inputOff;
} }
Buffer.BlockCopy(this.inputBuf, this.inputOff, this.window, this.strstart + this.lookahead, more); Unsafe.CopyBlockUnaligned(
ref this.window.Span[this.strstart + this.lookahead],
ref this.inputBuf[this.inputOff],
unchecked((uint)more));
this.inputOff += more; this.inputOff += more;
this.lookahead += more; this.lookahead += more;
@ -426,7 +429,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
private void SlideWindow() private void SlideWindow()
{ {
Unsafe.CopyBlockUnaligned(ref this.window[0], ref this.window[DeflaterConstants.WSIZE], DeflaterConstants.WSIZE); Unsafe.CopyBlockUnaligned(
ref this.window.Span[0],
ref this.window.Span[DeflaterConstants.WSIZE],
DeflaterConstants.WSIZE);
this.matchStart -= DeflaterConstants.WSIZE; this.matchStart -= DeflaterConstants.WSIZE;
this.strstart -= DeflaterConstants.WSIZE; this.strstart -= DeflaterConstants.WSIZE;
this.blockStart -= DeflaterConstants.WSIZE; this.blockStart -= DeflaterConstants.WSIZE;
@ -663,7 +670,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
lastBlock = false; lastBlock = false;
} }
this.huffman.FlushStoredBlock(this.window, this.blockStart, storedLength, lastBlock); this.huffman.FlushStoredBlock(this.window.Span, this.blockStart, storedLength, lastBlock);
this.blockStart += storedLength; this.blockStart += storedLength;
return !(lastBlock || storedLength == 0); return !(lastBlock || storedLength == 0);
} }
@ -683,7 +690,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
if (this.lookahead == 0) if (this.lookahead == 0)
{ {
// We are flushing everything // We are flushing everything
this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, finish); this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, finish);
this.blockStart = this.strstart; this.blockStart = this.strstart;
return false; return false;
} }
@ -743,7 +750,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
if (this.huffman.IsFull()) if (this.huffman.IsFull())
{ {
bool lastBlock = finish && (this.lookahead == 0); bool lastBlock = finish && (this.lookahead == 0);
this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, lastBlock); this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, lastBlock);
this.blockStart = this.strstart; this.blockStart = this.strstart;
return !lastBlock; return !lastBlock;
} }
@ -771,7 +778,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.prevAvailable = false; this.prevAvailable = false;
// We are flushing everything // We are flushing everything
this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, finish); this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, finish);
this.blockStart = this.strstart; this.blockStart = this.strstart;
return false; return false;
} }
@ -846,7 +853,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
} }
bool lastBlock = finish && (this.lookahead == 0) && !this.prevAvailable; bool lastBlock = finish && (this.lookahead == 0) && !this.prevAvailable;
this.huffman.FlushBlock(this.window, this.blockStart, len, lastBlock); this.huffman.FlushBlock(this.window.Span, this.blockStart, len, lastBlock);
this.blockStart += len; this.blockStart += len;
return !lastBlock; return !lastBlock;
} }

30
src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs → src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs

@ -7,7 +7,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib namespace SixLabors.ImageSharp.Compression.Zlib
{ {
/// <summary> /// <summary>
/// Performs Deflate Huffman encoding. /// Performs Deflate Huffman encoding.
@ -41,11 +41,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
private Tree blTree; private Tree blTree;
// Buffer for distances // Buffer for distances
private readonly IMemoryOwner<short> distanceManagedBuffer; private readonly IMemoryOwner<short> distanceMemoryOwner;
private readonly short* pinnedDistanceBuffer; private readonly short* pinnedDistanceBuffer;
private MemoryHandle distanceBufferHandle; private MemoryHandle distanceBufferHandle;
private readonly IMemoryOwner<short> literalManagedBuffer; private readonly IMemoryOwner<short> literalMemoryOwner;
private readonly short* pinnedLiteralBuffer; private readonly short* pinnedLiteralBuffer;
private MemoryHandle literalBufferHandle; private MemoryHandle literalBufferHandle;
@ -65,12 +65,12 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.distTree = new Tree(memoryAllocator, DistanceNumber, 1, 15); this.distTree = new Tree(memoryAllocator, DistanceNumber, 1, 15);
this.blTree = new Tree(memoryAllocator, BitLengthNumber, 4, 7); this.blTree = new Tree(memoryAllocator, BitLengthNumber, 4, 7);
this.distanceManagedBuffer = memoryAllocator.Allocate<short>(BufferSize); this.distanceMemoryOwner = memoryAllocator.Allocate<short>(BufferSize);
this.distanceBufferHandle = this.distanceManagedBuffer.Memory.Pin(); this.distanceBufferHandle = this.distanceMemoryOwner.Memory.Pin();
this.pinnedDistanceBuffer = (short*)this.distanceBufferHandle.Pointer; this.pinnedDistanceBuffer = (short*)this.distanceBufferHandle.Pointer;
this.literalManagedBuffer = memoryAllocator.Allocate<short>(BufferSize); this.literalMemoryOwner = memoryAllocator.Allocate<short>(BufferSize);
this.literalBufferHandle = this.literalManagedBuffer.Memory.Pin(); this.literalBufferHandle = this.literalMemoryOwner.Memory.Pin();
this.pinnedLiteralBuffer = (short*)this.literalBufferHandle.Pointer; this.pinnedLiteralBuffer = (short*)this.literalBufferHandle.Pointer;
} }
@ -239,7 +239,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <param name="storedLength">Count of bytes to write</param> /// <param name="storedLength">Count of bytes to write</param>
/// <param name="lastBlock">True if this is the last block</param> /// <param name="lastBlock">True if this is the last block</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void FlushStoredBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock) public void FlushStoredBlock(ReadOnlySpan<byte> stored, int storedOffset, int storedLength, bool lastBlock)
{ {
this.Pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + (lastBlock ? 1 : 0), 3); this.Pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + (lastBlock ? 1 : 0), 3);
this.Pending.AlignToByte(); this.Pending.AlignToByte();
@ -256,7 +256,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <param name="storedOffset">Index of first byte to flush</param> /// <param name="storedOffset">Index of first byte to flush</param>
/// <param name="storedLength">Count of bytes to flush</param> /// <param name="storedLength">Count of bytes to flush</param>
/// <param name="lastBlock">True if this is the last block</param> /// <param name="lastBlock">True if this is the last block</param>
public void FlushBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock) public void FlushBlock(ReadOnlySpan<byte> stored, int storedOffset, int storedLength, bool lastBlock)
{ {
this.literalTree.Frequencies[EofSymbol]++; this.literalTree.Frequencies[EofSymbol]++;
@ -286,13 +286,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
+ this.extraBits; + this.extraBits;
int static_len = this.extraBits; int static_len = this.extraBits;
ref byte staticLLengthRef = ref MemoryMarshal.GetReference<byte>(StaticLLength); ref byte staticLLengthRef = ref MemoryMarshal.GetReference(StaticLLength);
for (int i = 0; i < LiteralNumber; i++) for (int i = 0; i < LiteralNumber; i++)
{ {
static_len += this.literalTree.Frequencies[i] * Unsafe.Add(ref staticLLengthRef, i); static_len += this.literalTree.Frequencies[i] * Unsafe.Add(ref staticLLengthRef, i);
} }
ref byte staticDLengthRef = ref MemoryMarshal.GetReference<byte>(StaticDLength); ref byte staticDLengthRef = ref MemoryMarshal.GetReference(StaticDLength);
for (int i = 0; i < DistanceNumber; i++) for (int i = 0; i < DistanceNumber; i++)
{ {
static_len += this.distTree.Frequencies[i] * Unsafe.Add(ref staticDLengthRef, i); static_len += this.distTree.Frequencies[i] * Unsafe.Add(ref staticDLengthRef, i);
@ -419,9 +419,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
{ {
this.Pending.Dispose(); this.Pending.Dispose();
this.distanceBufferHandle.Dispose(); this.distanceBufferHandle.Dispose();
this.distanceManagedBuffer.Dispose(); this.distanceMemoryOwner.Dispose();
this.literalBufferHandle.Dispose(); this.literalBufferHandle.Dispose();
this.literalManagedBuffer.Dispose(); this.literalMemoryOwner.Dispose();
this.literalTree.Dispose(); this.literalTree.Dispose();
this.blTree.Dispose(); this.blTree.Dispose();
@ -484,7 +484,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
private IMemoryOwner<short> frequenciesMemoryOwner; private IMemoryOwner<short> frequenciesMemoryOwner;
private MemoryHandle frequenciesMemoryHandle; private MemoryHandle frequenciesMemoryHandle;
private IManagedByteBuffer lengthsMemoryOwner; private IMemoryOwner<byte> lengthsMemoryOwner;
private MemoryHandle lengthsMemoryHandle; private MemoryHandle lengthsMemoryHandle;
public Tree(MemoryAllocator memoryAllocator, int elements, int minCodes, int maxLength) public Tree(MemoryAllocator memoryAllocator, int elements, int minCodes, int maxLength)
@ -498,7 +498,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.frequenciesMemoryHandle = this.frequenciesMemoryOwner.Memory.Pin(); this.frequenciesMemoryHandle = this.frequenciesMemoryOwner.Memory.Pin();
this.Frequencies = (short*)this.frequenciesMemoryHandle.Pointer; this.Frequencies = (short*)this.frequenciesMemoryHandle.Pointer;
this.lengthsMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(elements); this.lengthsMemoryOwner = memoryAllocator.Allocate<byte>(elements);
this.lengthsMemoryHandle = this.lengthsMemoryOwner.Memory.Pin(); this.lengthsMemoryHandle = this.lengthsMemoryOwner.Memory.Pin();
this.Length = (byte*)this.lengthsMemoryHandle.Pointer; this.Length = (byte*)this.lengthsMemoryHandle.Pointer;

29
src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs → src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs

@ -2,10 +2,11 @@
// 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.IO; using System.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib namespace SixLabors.ImageSharp.Compression.Zlib
{ {
/// <summary> /// <summary>
/// A special stream deflating or compressing the bytes that are /// A special stream deflating or compressing the bytes that are
@ -14,8 +15,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
internal sealed class DeflaterOutputStream : Stream internal sealed class DeflaterOutputStream : Stream
{ {
private const int BufferLength = 512; private const int BufferLength = 512;
private IManagedByteBuffer memoryOwner; private IMemoryOwner<byte> memoryOwner;
private readonly byte[] buffer; private readonly Memory<byte> buffer;
private Deflater deflater; private Deflater deflater;
private readonly Stream rawStream; private readonly Stream rawStream;
private bool isDisposed; private bool isDisposed;
@ -29,8 +30,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
public DeflaterOutputStream(MemoryAllocator memoryAllocator, Stream rawStream, int compressionLevel) public DeflaterOutputStream(MemoryAllocator memoryAllocator, Stream rawStream, int compressionLevel)
{ {
this.rawStream = rawStream; this.rawStream = rawStream;
this.memoryOwner = memoryAllocator.AllocateManagedByteBuffer(BufferLength); this.memoryOwner = memoryAllocator.Allocate<byte>(BufferLength);
this.buffer = this.memoryOwner.Array; this.buffer = this.memoryOwner.Memory;
this.deflater = new Deflater(memoryAllocator, compressionLevel); this.deflater = new Deflater(memoryAllocator, compressionLevel);
} }
@ -49,15 +50,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <inheritdoc/> /// <inheritdoc/>
public override long Position public override long Position
{ {
get get => this.rawStream.Position;
{
return this.rawStream.Position;
}
set set => throw new NotSupportedException();
{
throw new NotSupportedException();
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -93,14 +88,14 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
{ {
while (flushing || !this.deflater.IsNeedingInput) while (flushing || !this.deflater.IsNeedingInput)
{ {
int deflateCount = this.deflater.Deflate(this.buffer, 0, BufferLength); int deflateCount = this.deflater.Deflate(this.buffer.Span, 0, BufferLength);
if (deflateCount <= 0) if (deflateCount <= 0)
{ {
break; break;
} }
this.rawStream.Write(this.buffer, 0, deflateCount); this.rawStream.Write(this.buffer.Span.Slice(0, deflateCount));
} }
if (!this.deflater.IsNeedingInput) if (!this.deflater.IsNeedingInput)
@ -114,13 +109,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.deflater.Finish(); this.deflater.Finish();
while (!this.deflater.IsFinished) while (!this.deflater.IsFinished)
{ {
int len = this.deflater.Deflate(this.buffer, 0, BufferLength); int len = this.deflater.Deflate(this.buffer.Span, 0, BufferLength);
if (len <= 0) if (len <= 0)
{ {
break; break;
} }
this.rawStream.Write(this.buffer, 0, len); this.rawStream.Write(this.buffer.Span.Slice(0, len));
} }
if (!this.deflater.IsFinished) if (!this.deflater.IsFinished)

33
src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs → src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs

@ -4,18 +4,19 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib namespace SixLabors.ImageSharp.Compression.Zlib
{ {
/// <summary> /// <summary>
/// Stores pending data for writing data to the Deflater. /// Stores pending data for writing data to the Deflater.
/// </summary> /// </summary>
internal sealed unsafe class DeflaterPendingBuffer : IDisposable internal sealed unsafe class DeflaterPendingBuffer : IDisposable
{ {
private readonly byte[] buffer; private readonly Memory<byte> buffer;
private readonly byte* pinnedBuffer; private readonly byte* pinnedBuffer;
private IManagedByteBuffer bufferMemoryOwner; private IMemoryOwner<byte> bufferMemoryOwner;
private MemoryHandle bufferMemoryHandle; private MemoryHandle bufferMemoryHandle;
private int start; private int start;
@ -29,9 +30,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <param name="memoryAllocator">The memory allocator to use for buffer allocations.</param> /// <param name="memoryAllocator">The memory allocator to use for buffer allocations.</param>
public DeflaterPendingBuffer(MemoryAllocator memoryAllocator) public DeflaterPendingBuffer(MemoryAllocator memoryAllocator)
{ {
this.bufferMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(DeflaterConstants.PENDING_BUF_SIZE); this.bufferMemoryOwner = memoryAllocator.Allocate<byte>(DeflaterConstants.PENDING_BUF_SIZE);
this.buffer = this.bufferMemoryOwner.Array; this.buffer = this.bufferMemoryOwner.Memory;
this.bufferMemoryHandle = this.bufferMemoryOwner.Memory.Pin(); this.bufferMemoryHandle = this.buffer.Pin();
this.pinnedBuffer = (byte*)this.bufferMemoryHandle.Pointer; this.pinnedBuffer = (byte*)this.bufferMemoryHandle.Pointer;
} }
@ -70,9 +71,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <param name="offset">The offset of first byte to write.</param> /// <param name="offset">The offset of first byte to write.</param>
/// <param name="length">The number of bytes to write.</param> /// <param name="length">The number of bytes to write.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void WriteBlock(byte[] block, int offset, int length) public void WriteBlock(ReadOnlySpan<byte> block, int offset, int length)
{ {
Unsafe.CopyBlockUnaligned(ref this.buffer[this.end], ref block[offset], unchecked((uint)length)); Unsafe.CopyBlockUnaligned(
ref this.buffer.Span[this.end],
ref MemoryMarshal.GetReference(block.Slice(offset)),
unchecked((uint)length));
this.end += length; this.end += length;
} }
@ -136,7 +141,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <param name="offset">The offset into output array.</param> /// <param name="offset">The offset into output array.</param>
/// <param name="length">The maximum number of bytes to store.</param> /// <param name="length">The maximum number of bytes to store.</param>
/// <returns>The number of bytes flushed.</returns> /// <returns>The number of bytes flushed.</returns>
public int Flush(byte[] output, int offset, int length) public int Flush(Span<byte> output, int offset, int length)
{ {
if (this.BitCount >= 8) if (this.BitCount >= 8)
{ {
@ -149,13 +154,19 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
{ {
length = this.end - this.start; length = this.end - this.start;
Unsafe.CopyBlockUnaligned(ref output[offset], ref this.buffer[this.start], unchecked((uint)length)); Unsafe.CopyBlockUnaligned(
ref output[offset],
ref this.buffer.Span[this.start],
unchecked((uint)length));
this.start = 0; this.start = 0;
this.end = 0; this.end = 0;
} }
else else
{ {
Unsafe.CopyBlockUnaligned(ref output[offset], ref this.buffer[this.start], unchecked((uint)length)); Unsafe.CopyBlockUnaligned(
ref output[offset],
ref this.buffer.Span[this.start],
unchecked((uint)length));
this.start += length; this.start += length;
} }

0
src/ImageSharp/Formats/Png/Zlib/README.md → src/ImageSharp/Compression/Zlib/README.md

15
src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs → src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs

@ -4,9 +4,10 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib namespace SixLabors.ImageSharp.Compression.Zlib
{ {
/// <summary> /// <summary>
/// Provides methods and properties for compressing streams by using the Zlib Deflate algorithm. /// Provides methods and properties for compressing streams by using the Zlib Deflate algorithm.
@ -39,9 +40,19 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <summary> /// <summary>
/// The stream responsible for compressing the input stream. /// The stream responsible for compressing the input stream.
/// </summary> /// </summary>
// private DeflateStream deflateStream;
private DeflaterOutputStream deflateStream; private DeflaterOutputStream deflateStream;
/// <summary>
/// Initializes a new instance of the <see cref="ZlibDeflateStream"/> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator to use for buffer allocations.</param>
/// <param name="stream">The stream to compress.</param>
/// <param name="level">The compression level.</param>
public ZlibDeflateStream(MemoryAllocator memoryAllocator, Stream stream, DeflateCompressionLevel level)
: this(memoryAllocator, stream, (PngCompressionLevel)level)
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ZlibDeflateStream"/> class. /// Initializes a new instance of the <see cref="ZlibDeflateStream"/> class.
/// </summary> /// </summary>

2
src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs → src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs

@ -6,7 +6,7 @@ using System.IO;
using System.IO.Compression; using System.IO.Compression;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Formats.Png.Zlib namespace SixLabors.ImageSharp.Compression.Zlib
{ {
/// <summary> /// <summary>
/// Provides methods and properties for deframing streams from PNGs. /// Provides methods and properties for deframing streams from PNGs.

0
src/ImageSharp/Formats/Png/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf → src/ImageSharp/Compression/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf

44
src/ImageSharp/Configuration.cs

@ -10,6 +10,8 @@ using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
@ -39,7 +41,7 @@ namespace SixLabors.ImageSharp
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Configuration" /> class. /// Initializes a new instance of the <see cref="Configuration" /> class.
/// </summary> /// </summary>
/// <param name="configurationModules">A collection of configuration modules to register</param> /// <param name="configurationModules">A collection of configuration modules to register.</param>
public Configuration(params IConfigurationModule[] configurationModules) public Configuration(params IConfigurationModule[] configurationModules)
{ {
if (configurationModules != null) if (configurationModules != null)
@ -77,7 +79,7 @@ namespace SixLabors.ImageSharp
/// <summary> /// <summary>
/// Gets or sets the size of the buffer to use when working with streams. /// Gets or sets the size of the buffer to use when working with streams.
/// Intitialized with <see cref="DefaultStreamProcessingBufferSize"/> by default. /// Initialized with <see cref="DefaultStreamProcessingBufferSize"/> by default.
/// </summary> /// </summary>
public int StreamProcessingBufferSize public int StreamProcessingBufferSize
{ {
@ -94,9 +96,9 @@ namespace SixLabors.ImageSharp
} }
/// <summary> /// <summary>
/// Gets a set of properties for the Congiguration. /// Gets a set of properties for the Configuration.
/// </summary> /// </summary>
/// <remarks>This can be used for storing global settings and defaults to be accessable to processors.</remarks> /// <remarks>This can be used for storing global settings and defaults to be accessible to processors.</remarks>
public IDictionary<object, object> Properties { get; } = new ConcurrentDictionary<object, object>(); public IDictionary<object, object> Properties { get; } = new ConcurrentDictionary<object, object>();
/// <summary> /// <summary>
@ -158,20 +160,17 @@ namespace SixLabors.ImageSharp
/// Creates a shallow copy of the <see cref="Configuration"/>. /// Creates a shallow copy of the <see cref="Configuration"/>.
/// </summary> /// </summary>
/// <returns>A new configuration instance.</returns> /// <returns>A new configuration instance.</returns>
public Configuration Clone() public Configuration Clone() => new Configuration
{ {
return new Configuration MaxDegreeOfParallelism = this.MaxDegreeOfParallelism,
{ StreamProcessingBufferSize = this.StreamProcessingBufferSize,
MaxDegreeOfParallelism = this.MaxDegreeOfParallelism, ImageFormatsManager = this.ImageFormatsManager,
StreamProcessingBufferSize = this.StreamProcessingBufferSize, MemoryAllocator = this.MemoryAllocator,
ImageFormatsManager = this.ImageFormatsManager, ImageOperationsProvider = this.ImageOperationsProvider,
MemoryAllocator = this.MemoryAllocator, ReadOrigin = this.ReadOrigin,
ImageOperationsProvider = this.ImageOperationsProvider, FileSystem = this.FileSystem,
ReadOrigin = this.ReadOrigin, WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInBytes,
FileSystem = this.FileSystem, };
WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInBytes,
};
}
/// <summary> /// <summary>
/// Creates the default instance with the following <see cref="IConfigurationModule"/>s preregistered: /// Creates the default instance with the following <see cref="IConfigurationModule"/>s preregistered:
@ -180,16 +179,17 @@ namespace SixLabors.ImageSharp
/// <see cref="GifConfigurationModule"/> /// <see cref="GifConfigurationModule"/>
/// <see cref="BmpConfigurationModule"/>. /// <see cref="BmpConfigurationModule"/>.
/// <see cref="TgaConfigurationModule"/>. /// <see cref="TgaConfigurationModule"/>.
/// <see cref="TiffConfigurationModule"/>.
/// <see cref="WebpConfigurationModule"/>.
/// </summary> /// </summary>
/// <returns>The default configuration of <see cref="Configuration"/>.</returns> /// <returns>The default configuration of <see cref="Configuration"/>.</returns>
internal static Configuration CreateDefaultInstance() internal static Configuration CreateDefaultInstance() => new Configuration(
{
return new Configuration(
new PngConfigurationModule(), new PngConfigurationModule(),
new JpegConfigurationModule(), new JpegConfigurationModule(),
new GifConfigurationModule(), new GifConfigurationModule(),
new BmpConfigurationModule(), new BmpConfigurationModule(),
new TgaConfigurationModule()); new TgaConfigurationModule(),
} new TiffConfigurationModule(),
new WebpConfigurationModule());
} }
} }

12
src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.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.Bmp namespace SixLabors.ImageSharp.Formats.Bmp
@ -8,6 +8,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary> /// </summary>
public enum BmpBitsPerPixel : short public enum BmpBitsPerPixel : short
{ {
/// <summary>
/// 1 bit per pixel.
/// </summary>
Pixel1 = 1,
/// <summary>
/// 4 bits per pixel.
/// </summary>
Pixel4 = 4,
/// <summary> /// <summary>
/// 8 bits per pixel. Each pixel consists of 1 byte. /// 8 bits per pixel. Each pixel consists of 1 byte.
/// </summary> /// </summary>

320
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -817,31 +817,29 @@ namespace SixLabors.ImageSharp.Formats.Bmp
padding = 4 - padding; padding = 4 - padding;
} }
using (IManagedByteBuffer row = this.memoryAllocator.AllocateManagedByteBuffer(arrayWidth + padding, AllocationOptions.Clean)) using IMemoryOwner<byte> row = this.memoryAllocator.Allocate<byte>(arrayWidth + padding, AllocationOptions.Clean);
TPixel color = default;
Span<byte> rowSpan = row.GetSpan();
for (int y = 0; y < height; y++)
{ {
TPixel color = default; int newY = Invert(y, height, inverted);
Span<byte> rowSpan = row.GetSpan(); this.stream.Read(rowSpan);
int offset = 0;
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
for (int y = 0; y < height; y++) for (int x = 0; x < arrayWidth; x++)
{ {
int newY = Invert(y, height, inverted); int colOffset = x * ppb;
this.stream.Read(row.Array, 0, row.Length()); for (int shift = 0, newX = colOffset; shift < ppb && newX < width; shift++, newX++)
int offset = 0;
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
for (int x = 0; x < arrayWidth; x++)
{ {
int colOffset = x * ppb; int colorIndex = ((rowSpan[offset] >> (8 - bitsPerPixel - (shift * bitsPerPixel))) & mask) * bytesPerColorMapEntry;
for (int shift = 0, newX = colOffset; shift < ppb && newX < width; shift++, newX++)
{
int colorIndex = ((rowSpan[offset] >> (8 - bitsPerPixel - (shift * bitsPerPixel))) & mask) * bytesPerColorMapEntry;
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIndex]));
pixelRow[newX] = color;
}
offset++; color.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIndex]));
pixelRow[newX] = color;
} }
offset++;
} }
} }
} }
@ -873,29 +871,29 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int greenMaskBits = CountBits((uint)greenMask); int greenMaskBits = CountBits((uint)greenMask);
int blueMaskBits = CountBits((uint)blueMask); int blueMaskBits = CountBits((uint)blueMask);
using (IManagedByteBuffer buffer = this.memoryAllocator.AllocateManagedByteBuffer(stride)) using IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(stride);
Span<byte> bufferSpan = buffer.GetSpan();
for (int y = 0; y < height; y++)
{ {
for (int y = 0; y < height; y++) this.stream.Read(bufferSpan);
{ int newY = Invert(y, height, inverted);
this.stream.Read(buffer.Array, 0, stride); Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
int offset = 0; int offset = 0;
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
short temp = BitConverter.ToInt16(buffer.Array, offset); short temp = BinaryPrimitives.ReadInt16LittleEndian(bufferSpan.Slice(offset));
// Rescale values, so the values range from 0 to 255. // Rescale values, so the values range from 0 to 255.
int r = (redMaskBits == 5) ? GetBytesFrom5BitValue((temp & redMask) >> rightShiftRedMask) : GetBytesFrom6BitValue((temp & redMask) >> rightShiftRedMask); int r = (redMaskBits == 5) ? GetBytesFrom5BitValue((temp & redMask) >> rightShiftRedMask) : GetBytesFrom6BitValue((temp & redMask) >> rightShiftRedMask);
int g = (greenMaskBits == 5) ? GetBytesFrom5BitValue((temp & greenMask) >> rightShiftGreenMask) : GetBytesFrom6BitValue((temp & greenMask) >> rightShiftGreenMask); int g = (greenMaskBits == 5) ? GetBytesFrom5BitValue((temp & greenMask) >> rightShiftGreenMask) : GetBytesFrom6BitValue((temp & greenMask) >> rightShiftGreenMask);
int b = (blueMaskBits == 5) ? GetBytesFrom5BitValue((temp & blueMask) >> rightShiftBlueMask) : GetBytesFrom6BitValue((temp & blueMask) >> rightShiftBlueMask); int b = (blueMaskBits == 5) ? GetBytesFrom5BitValue((temp & blueMask) >> rightShiftBlueMask) : GetBytesFrom6BitValue((temp & blueMask) >> rightShiftBlueMask);
var rgb = new Rgb24((byte)r, (byte)g, (byte)b); var rgb = new Rgb24((byte)r, (byte)g, (byte)b);
color.FromRgb24(rgb); color.FromRgb24(rgb);
pixelRow[x] = color; pixelRow[x] = color;
offset += 2; offset += 2;
}
} }
} }
} }
@ -928,20 +926,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int padding = CalculatePadding(width, 3); int padding = CalculatePadding(width, 3);
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, padding);
Span<byte> rowSpan = row.GetSpan();
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, padding)) for (int y = 0; y < height; y++)
{ {
for (int y = 0; y < height; y++) this.stream.Read(rowSpan);
{ int newY = Invert(y, height, inverted);
this.stream.Read(row); Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
int newY = Invert(y, height, inverted); PixelOperations<TPixel>.Instance.FromBgr24Bytes(
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY); this.Configuration,
PixelOperations<TPixel>.Instance.FromBgr24Bytes( rowSpan,
this.Configuration, pixelSpan,
row.GetSpan(), width);
pixelSpan,
width);
}
} }
} }
@ -957,20 +954,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int padding = CalculatePadding(width, 4); int padding = CalculatePadding(width, 4);
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding);
Span<byte> rowSpan = row.GetSpan();
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding)) for (int y = 0; y < height; y++)
{ {
for (int y = 0; y < height; y++) this.stream.Read(rowSpan);
{ int newY = Invert(y, height, inverted);
this.stream.Read(row); Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
int newY = Invert(y, height, inverted); PixelOperations<TPixel>.Instance.FromBgra32Bytes(
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY); this.Configuration,
PixelOperations<TPixel>.Instance.FromBgra32Bytes( rowSpan,
this.Configuration, pixelSpan,
row.GetSpan(), width);
pixelSpan,
width);
}
} }
} }
@ -987,87 +983,85 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int padding = CalculatePadding(width, 4); int padding = CalculatePadding(width, 4);
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding);
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding)) using IMemoryOwner<Bgra32> bgraRow = this.memoryAllocator.Allocate<Bgra32>(width);
using (IMemoryOwner<Bgra32> bgraRow = this.memoryAllocator.Allocate<Bgra32>(width)) Span<byte> rowSpan = row.GetSpan();
Span<Bgra32> bgraRowSpan = bgraRow.GetSpan();
long currentPosition = this.stream.Position;
bool hasAlpha = false;
// Loop though the rows checking each pixel. We start by assuming it's
// an BGR0 image. If we hit a non-zero alpha value, then we know it's
// actually a BGRA image, and change tactics accordingly.
for (int y = 0; y < height; y++)
{ {
Span<Bgra32> bgraRowSpan = bgraRow.GetSpan(); this.stream.Read(rowSpan);
long currentPosition = this.stream.Position;
bool hasAlpha = false;
// Loop though the rows checking each pixel. We start by assuming it's PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
// an BGR0 image. If we hit a non-zero alpha value, then we know it's this.Configuration,
// actually a BGRA image, and change tactics accordingly. rowSpan,
for (int y = 0; y < height; y++) bgraRowSpan,
{ width);
this.stream.Read(row);
PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.Configuration,
row.GetSpan(),
bgraRowSpan,
width);
// Check each pixel in the row to see if it has an alpha value.
for (int x = 0; x < width; x++)
{
Bgra32 bgra = bgraRowSpan[x];
if (bgra.A > 0)
{
hasAlpha = true;
break;
}
}
if (hasAlpha) // Check each pixel in the row to see if it has an alpha value.
for (int x = 0; x < width; x++)
{
Bgra32 bgra = bgraRowSpan[x];
if (bgra.A > 0)
{ {
hasAlpha = true;
break; break;
} }
} }
// Reset our stream for a second pass.
this.stream.Position = currentPosition;
// Process the pixels in bulk taking the raw alpha component value.
if (hasAlpha) if (hasAlpha)
{ {
for (int y = 0; y < height; y++) break;
{
this.stream.Read(row);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(
this.Configuration,
row.GetSpan(),
pixelSpan,
width);
}
return;
} }
}
// Slow path. We need to set each alpha component value to fully opaque. // Reset our stream for a second pass.
this.stream.Position = currentPosition;
// Process the pixels in bulk taking the raw alpha component value.
if (hasAlpha)
{
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
this.stream.Read(row); this.stream.Read(rowSpan);
PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.Configuration,
row.GetSpan(),
bgraRowSpan,
width);
int newY = Invert(y, height, inverted); int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY); Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
for (int x = 0; x < width; x++) PixelOperations<TPixel>.Instance.FromBgra32Bytes(
{ this.Configuration,
Bgra32 bgra = bgraRowSpan[x]; rowSpan,
bgra.A = byte.MaxValue; pixelSpan,
ref TPixel pixel = ref pixelSpan[x]; width);
pixel.FromBgra32(bgra); }
}
return;
}
// Slow path. We need to set each alpha component value to fully opaque.
for (int y = 0; y < height; y++)
{
this.stream.Read(rowSpan);
PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.Configuration,
rowSpan,
bgraRowSpan,
width);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
for (int x = 0; x < width; x++)
{
Bgra32 bgra = bgraRowSpan[x];
bgra.A = byte.MaxValue;
ref TPixel pixel = ref pixelSpan[x];
pixel.FromBgra32(bgra);
} }
} }
} }
@ -1108,44 +1102,44 @@ namespace SixLabors.ImageSharp.Formats.Bmp
bool unusualBitMask = bitsRedMask > 8 || bitsGreenMask > 8 || bitsBlueMask > 8 || invMaxValueAlpha > 8; bool unusualBitMask = bitsRedMask > 8 || bitsGreenMask > 8 || bitsBlueMask > 8 || invMaxValueAlpha > 8;
using (IManagedByteBuffer buffer = this.memoryAllocator.AllocateManagedByteBuffer(stride)) using IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(stride);
Span<byte> bufferSpan = buffer.GetSpan();
for (int y = 0; y < height; y++)
{ {
for (int y = 0; y < height; y++) this.stream.Read(bufferSpan);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
int offset = 0;
for (int x = 0; x < width; x++)
{ {
this.stream.Read(buffer.Array, 0, stride); uint temp = BinaryPrimitives.ReadUInt32LittleEndian(bufferSpan.Slice(offset));
int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
int offset = 0; if (unusualBitMask)
for (int x = 0; x < width; x++)
{ {
uint temp = BitConverter.ToUInt32(buffer.Array, offset); uint r = (uint)(temp & redMask) >> rightShiftRedMask;
uint g = (uint)(temp & greenMask) >> rightShiftGreenMask;
if (unusualBitMask) uint b = (uint)(temp & blueMask) >> rightShiftBlueMask;
{ float alpha = alphaMask != 0 ? invMaxValueAlpha * ((uint)(temp & alphaMask) >> rightShiftAlphaMask) : 1.0f;
uint r = (uint)(temp & redMask) >> rightShiftRedMask; var vector4 = new Vector4(
uint g = (uint)(temp & greenMask) >> rightShiftGreenMask; r * invMaxValueRed,
uint b = (uint)(temp & blueMask) >> rightShiftBlueMask; g * invMaxValueGreen,
float alpha = alphaMask != 0 ? invMaxValueAlpha * ((uint)(temp & alphaMask) >> rightShiftAlphaMask) : 1.0f; b * invMaxValueBlue,
var vector4 = new Vector4( alpha);
r * invMaxValueRed, color.FromVector4(vector4);
g * invMaxValueGreen,
b * invMaxValueBlue,
alpha);
color.FromVector4(vector4);
}
else
{
byte r = (byte)((temp & redMask) >> rightShiftRedMask);
byte g = (byte)((temp & greenMask) >> rightShiftGreenMask);
byte b = (byte)((temp & blueMask) >> rightShiftBlueMask);
byte a = alphaMask != 0 ? (byte)((temp & alphaMask) >> rightShiftAlphaMask) : (byte)255;
color.FromRgba32(new Rgba32(r, g, b, a));
}
pixelRow[x] = color;
offset += 4;
} }
else
{
byte r = (byte)((temp & redMask) >> rightShiftRedMask);
byte g = (byte)((temp & greenMask) >> rightShiftGreenMask);
byte b = (byte)((temp & blueMask) >> rightShiftBlueMask);
byte a = alphaMask != 0 ? (byte)((temp & alphaMask) >> rightShiftAlphaMask) : (byte)255;
color.FromRgba32(new Rgba32(r, g, b, a));
}
pixelRow[x] = color;
offset += 4;
} }
} }
} }
@ -1303,15 +1297,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
short bitsPerPixel = this.infoHeader.BitsPerPixel; short bitsPerPixel = this.infoHeader.BitsPerPixel;
this.bmpMetadata = this.metadata.GetBmpMetadata(); this.bmpMetadata = this.metadata.GetBmpMetadata();
this.bmpMetadata.InfoHeaderType = infoHeaderType; this.bmpMetadata.InfoHeaderType = infoHeaderType;
this.bmpMetadata.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel;
// We can only encode at these bit rates so far (1 bit and 4 bit are still missing).
if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel8)
|| bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel16)
|| bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel24)
|| bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel32))
{
this.bmpMetadata.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel;
}
} }
/// <summary> /// <summary>

2
src/ImageSharp/Formats/Bmp/BmpEncoder.cs

@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary> /// <summary>
/// Gets or sets the quantizer for reducing the color count for 8-Bit images. /// Gets or sets the quantizer for reducing the color count for 8-Bit images.
/// Defaults to OctreeQuantizer. /// Defaults to Wu Quantizer.
/// </summary> /// </summary>
public IQuantizer Quantizer { get; set; } public IQuantizer Quantizer { get; set; }

273
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -51,6 +51,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary> /// </summary>
private const int ColorPaletteSize8Bit = 1024; private const int ColorPaletteSize8Bit = 1024;
/// <summary>
/// The color palette for an 4 bit image will have 16 entry's with 4 bytes for each entry.
/// </summary>
private const int ColorPaletteSize4Bit = 64;
/// <summary>
/// The color palette for an 1 bit image will have 2 entry's with 4 bytes for each entry.
/// </summary>
private const int ColorPaletteSize1Bit = 8;
/// <summary> /// <summary>
/// Used for allocating memory during processing operations. /// Used for allocating memory during processing operations.
/// </summary> /// </summary>
@ -74,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private readonly bool writeV4Header; private readonly bool writeV4Header;
/// <summary> /// <summary>
/// The quantizer for reducing the color count for 8-Bit images. /// The quantizer for reducing the color count for 8-Bit, 4-Bit and 1-Bit images.
/// </summary> /// </summary>
private readonly IQuantizer quantizer; private readonly IQuantizer quantizer;
@ -107,7 +117,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.configuration = image.GetConfiguration(); this.configuration = image.GetConfiguration();
ImageMetadata metadata = image.Metadata; ImageMetadata metadata = image.Metadata;
BmpMetadata bmpMetadata = metadata.GetBmpMetadata(); BmpMetadata bmpMetadata = metadata.GetBmpMetadata();
this.bitsPerPixel = this.bitsPerPixel ?? bmpMetadata.BitsPerPixel; this.bitsPerPixel ??= bmpMetadata.BitsPerPixel;
short bpp = (short)this.bitsPerPixel; short bpp = (short)this.bitsPerPixel;
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32);
@ -166,7 +176,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
infoHeader.Compression = BmpCompression.BitFields; infoHeader.Compression = BmpCompression.BitFields;
} }
int colorPaletteSize = this.bitsPerPixel == BmpBitsPerPixel.Pixel8 ? ColorPaletteSize8Bit : 0; int colorPaletteSize = 0;
if (this.bitsPerPixel == BmpBitsPerPixel.Pixel8)
{
colorPaletteSize = ColorPaletteSize8Bit;
}
else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel4)
{
colorPaletteSize = ColorPaletteSize4Bit;
}
else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel1)
{
colorPaletteSize = ColorPaletteSize1Bit;
}
var fileHeader = new BmpFileHeader( var fileHeader = new BmpFileHeader(
type: BmpConstants.TypeMarkers.Bitmap, type: BmpConstants.TypeMarkers.Bitmap,
@ -224,10 +246,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
case BmpBitsPerPixel.Pixel8: case BmpBitsPerPixel.Pixel8:
this.Write8Bit(stream, image); this.Write8Bit(stream, image);
break; break;
case BmpBitsPerPixel.Pixel4:
this.Write4BitColor(stream, image);
break;
case BmpBitsPerPixel.Pixel1:
this.Write1BitColor(stream, image);
break;
} }
} }
private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding); private IMemoryOwner<byte> AllocateRow(int width, int bytesPerPixel)
=> this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding);
/// <summary> /// <summary>
/// Writes the 32bit color palette to the stream. /// Writes the 32bit color palette to the stream.
@ -238,18 +269,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private void Write32Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels) private void Write32Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4)) using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 4);
Span<byte> rowSpan = row.GetSpan();
for (int y = pixels.Height - 1; y >= 0; y--)
{ {
for (int y = pixels.Height - 1; y >= 0; y--) Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
{ PixelOperations<TPixel>.Instance.ToBgra32Bytes(
Span<TPixel> pixelSpan = pixels.GetRowSpan(y); this.configuration,
PixelOperations<TPixel>.Instance.ToBgra32Bytes( pixelSpan,
this.configuration, rowSpan,
pixelSpan, pixelSpan.Length);
row.GetSpan(), stream.Write(rowSpan);
pixelSpan.Length);
stream.Write(row.Array, 0, row.Length());
}
} }
} }
@ -264,18 +295,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{ {
int width = pixels.Width; int width = pixels.Width;
int rowBytesWithoutPadding = width * 3; int rowBytesWithoutPadding = width * 3;
using (IManagedByteBuffer row = this.AllocateRow(width, 3)) using IMemoryOwner<byte> row = this.AllocateRow(width, 3);
Span<byte> rowSpan = row.GetSpan();
for (int y = pixels.Height - 1; y >= 0; y--)
{ {
for (int y = pixels.Height - 1; y >= 0; y--) Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
{ PixelOperations<TPixel>.Instance.ToBgr24Bytes(
Span<TPixel> pixelSpan = pixels.GetRowSpan(y); this.configuration,
PixelOperations<TPixel>.Instance.ToBgr24Bytes( pixelSpan,
this.configuration, row.Slice(0, rowBytesWithoutPadding),
pixelSpan, width);
row.Slice(0, rowBytesWithoutPadding), stream.Write(rowSpan);
width);
stream.Write(row.Array, 0, row.Length());
}
} }
} }
@ -290,25 +321,25 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{ {
int width = pixels.Width; int width = pixels.Width;
int rowBytesWithoutPadding = width * 2; int rowBytesWithoutPadding = width * 2;
using (IManagedByteBuffer row = this.AllocateRow(width, 2)) using IMemoryOwner<byte> row = this.AllocateRow(width, 2);
Span<byte> rowSpan = row.GetSpan();
for (int y = pixels.Height - 1; y >= 0; y--)
{ {
for (int y = pixels.Height - 1; y >= 0; y--) Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra5551Bytes( PixelOperations<TPixel>.Instance.ToBgra5551Bytes(
this.configuration, this.configuration,
pixelSpan, pixelSpan,
row.Slice(0, rowBytesWithoutPadding), row.Slice(0, rowBytesWithoutPadding),
pixelSpan.Length); pixelSpan.Length);
stream.Write(row.Array, 0, row.Length()); stream.Write(rowSpan);
}
} }
} }
/// <summary> /// <summary>
/// Writes an 8 Bit image with a color palette. The color palette has 256 entry's with 4 bytes for each entry. /// Writes an 8 bit image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="stream">The <see cref="Stream"/> to write to.</param>
@ -317,22 +348,21 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
bool isL8 = typeof(TPixel) == typeof(L8); bool isL8 = typeof(TPixel) == typeof(L8);
using (IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize8Bit, AllocationOptions.Clean)) using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize8Bit, AllocationOptions.Clean);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
if (isL8)
{ {
Span<byte> colorPalette = colorPaletteBuffer.GetSpan(); this.Write8BitGray(stream, image, colorPalette);
if (isL8) }
{ else
this.Write8BitGray(stream, image, colorPalette); {
} this.Write8BitColor(stream, image, colorPalette);
else
{
this.Write8BitColor(stream, image, colorPalette);
}
} }
} }
/// <summary> /// <summary>
/// Writes an 8 Bit color image with a color palette. The color palette has 256 entry's with 4 bytes for each entry. /// Writes an 8 bit color image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="stream">The <see cref="Stream"/> to write to.</param>
@ -344,16 +374,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration); using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
ReadOnlySpan<TPixel> quantizedColors = quantized.Palette.Span; ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
var quantizedColorBytes = quantizedColors.Length * 4; this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
PixelOperations<TPixel>.Instance.ToBgra32(this.configuration, quantizedColors, MemoryMarshal.Cast<byte, Bgra32>(colorPalette.Slice(0, quantizedColorBytes)));
Span<uint> colorPaletteAsUInt = MemoryMarshal.Cast<byte, uint>(colorPalette);
for (int i = 0; i < colorPaletteAsUInt.Length; i++)
{
colorPaletteAsUInt[i] = colorPaletteAsUInt[i] & 0x00FFFFFF; // Padding byte, always 0.
}
stream.Write(colorPalette);
for (int y = image.Height - 1; y >= 0; y--) for (int y = image.Height - 1; y >= 0; y--)
{ {
@ -368,7 +390,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
} }
/// <summary> /// <summary>
/// Writes an 8 Bit gray image with a color palette. The color palette has 256 entry's with 4 bytes for each entry. /// Writes an 8 bit gray image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param> /// <param name="stream">The <see cref="Stream"/> to write to.</param>
@ -404,5 +426,136 @@ namespace SixLabors.ImageSharp.Formats.Bmp
} }
} }
} }
/// <summary>
/// Writes an 4 bit color image with a color palette. The color palette has 16 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write4BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
{
MaxColors = 16
});
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize4Bit, AllocationOptions.Clean);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
ReadOnlySpan<byte> pixelRowSpan = quantized.GetPixelRowSpan(0);
int rowPadding = pixelRowSpan.Length % 2 != 0 ? this.padding - 1 : this.padding;
for (int y = image.Height - 1; y >= 0; y--)
{
pixelRowSpan = quantized.GetPixelRowSpan(y);
int endIdx = pixelRowSpan.Length % 2 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 1;
for (int i = 0; i < endIdx; i += 2)
{
stream.WriteByte((byte)((pixelRowSpan[i] << 4) | pixelRowSpan[i + 1]));
}
if (pixelRowSpan.Length % 2 != 0)
{
stream.WriteByte((byte)((pixelRowSpan[pixelRowSpan.Length - 1] << 4) | 0));
}
for (int i = 0; i < rowPadding; i++)
{
stream.WriteByte(0);
}
}
}
/// <summary>
/// Writes a 1 bit image with a color palette. The color palette has 2 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write1BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
{
MaxColors = 2
});
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize1Bit, AllocationOptions.Clean);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
ReadOnlySpan<byte> quantizedPixelRow = quantized.GetPixelRowSpan(0);
int rowPadding = quantizedPixelRow.Length % 8 != 0 ? this.padding - 1 : this.padding;
for (int y = image.Height - 1; y >= 0; y--)
{
quantizedPixelRow = quantized.GetPixelRowSpan(y);
int endIdx = quantizedPixelRow.Length % 8 == 0 ? quantizedPixelRow.Length : quantizedPixelRow.Length - 8;
for (int i = 0; i < endIdx; i += 8)
{
Write1BitPalette(stream, i, i + 8, quantizedPixelRow);
}
if (quantizedPixelRow.Length % 8 != 0)
{
int startIdx = quantizedPixelRow.Length - 7;
endIdx = quantizedPixelRow.Length;
Write1BitPalette(stream, startIdx, endIdx, quantizedPixelRow);
}
for (int i = 0; i < rowPadding; i++)
{
stream.WriteByte(0);
}
}
}
/// <summary>
/// Writes the color palette to the stream. The color palette has 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="quantizedColorPalette">The color palette from the quantized image.</param>
/// <param name="colorPalette">A temporary byte span to write the color palette to.</param>
private void WriteColorPalette<TPixel>(Stream stream, ReadOnlySpan<TPixel> quantizedColorPalette, Span<byte> colorPalette)
where TPixel : unmanaged, IPixel<TPixel>
{
int quantizedColorBytes = quantizedColorPalette.Length * 4;
PixelOperations<TPixel>.Instance.ToBgra32(this.configuration, quantizedColorPalette, MemoryMarshal.Cast<byte, Bgra32>(colorPalette.Slice(0, quantizedColorBytes)));
Span<uint> colorPaletteAsUInt = MemoryMarshal.Cast<byte, uint>(colorPalette);
for (int i = 0; i < colorPaletteAsUInt.Length; i++)
{
colorPaletteAsUInt[i] = colorPaletteAsUInt[i] & 0x00FFFFFF; // Padding byte, always 0.
}
stream.Write(colorPalette);
}
/// <summary>
/// Writes a 1-bit palette.
/// </summary>
/// <param name="stream">The stream to write the palette to.</param>
/// <param name="startIdx">The start index.</param>
/// <param name="endIdx">The end index.</param>
/// <param name="quantizedPixelRow">A quantized pixel row.</param>
private static void Write1BitPalette(Stream stream, int startIdx, int endIdx, ReadOnlySpan<byte> quantizedPixelRow)
{
int shift = 7;
byte indices = 0;
for (int j = startIdx; j < endIdx; j++)
{
indices = (byte)(indices | ((byte)(quantizedPixelRow[j] & 1) << shift));
shift--;
}
stream.WriteByte(indices);
}
} }
} }

4
src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.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.
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
bool SupportTransparency { get; } bool SupportTransparency { get; }
/// <summary> /// <summary>
/// Gets the quantizer for reducing the color count for 8-Bit images. /// Gets the quantizer for reducing the color count for 8-Bit, 4-Bit, and 1-Bit images.
/// </summary> /// </summary>
IQuantizer Quantizer { get; } IQuantizer Quantizer { get; }
} }

31
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -2,12 +2,12 @@
// 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.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary> /// <summary>
/// The global color table. /// The global color table.
/// </summary> /// </summary>
private IManagedByteBuffer globalColorTable; private IMemoryOwner<byte> globalColorTable;
/// <summary> /// <summary>
/// The area to restore. /// The area to restore.
@ -323,12 +323,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
continue; continue;
} }
using (IManagedByteBuffer commentsBuffer = this.MemoryAllocator.AllocateManagedByteBuffer(length)) using IMemoryOwner<byte> commentsBuffer = this.MemoryAllocator.Allocate<byte>(length);
{ Span<byte> commentsSpan = commentsBuffer.GetSpan();
this.stream.Read(commentsBuffer.Array, 0, length);
string commentPart = GifConstants.Encoding.GetString(commentsBuffer.Array, 0, length); this.stream.Read(commentsSpan);
stringBuilder.Append(commentPart); string commentPart = GifConstants.Encoding.GetString(commentsSpan);
} stringBuilder.Append(commentPart);
} }
if (stringBuilder.Length > 0) if (stringBuilder.Length > 0)
@ -348,7 +348,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{ {
this.ReadImageDescriptor(); this.ReadImageDescriptor();
IManagedByteBuffer localColorTable = null; IMemoryOwner<byte> localColorTable = null;
Buffer2D<byte> indices = null; Buffer2D<byte> indices = null;
try try
{ {
@ -356,8 +356,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (this.imageDescriptor.LocalColorTableFlag) if (this.imageDescriptor.LocalColorTableFlag)
{ {
int length = this.imageDescriptor.LocalColorTableSize * 3; int length = this.imageDescriptor.LocalColorTableSize * 3;
localColorTable = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean); localColorTable = this.Configuration.MemoryAllocator.Allocate<byte>(length, AllocationOptions.Clean);
this.stream.Read(localColorTable.Array, 0, length); this.stream.Read(localColorTable.GetSpan());
} }
indices = this.Configuration.MemoryAllocator.Allocate2D<byte>(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean); indices = this.Configuration.MemoryAllocator.Allocate2D<byte>(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean);
@ -441,6 +441,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
int descriptorRight = descriptorLeft + descriptor.Width; int descriptorRight = descriptorLeft + descriptor.Width;
bool transFlag = this.graphicsControlExtension.TransparencyFlag; bool transFlag = this.graphicsControlExtension.TransparencyFlag;
byte transIndex = this.graphicsControlExtension.TransparencyIndex; byte transIndex = this.graphicsControlExtension.TransparencyIndex;
int colorTableMaxIdx = colorTable.Length - 1;
for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++) for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++)
{ {
@ -487,7 +488,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
// #403 The left + width value can be larger than the image width // #403 The left + width value can be larger than the image width
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++) for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
{ {
int index = Unsafe.Add(ref indicesRowRef, x - descriptorLeft); int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, x - descriptorLeft), 0, colorTableMaxIdx);
ref TPixel pixel = ref Unsafe.Add(ref rowRef, x); ref TPixel pixel = ref Unsafe.Add(ref rowRef, x);
Rgb24 rgb = colorTable[index]; Rgb24 rgb = colorTable[index];
pixel.FromRgb24(rgb); pixel.FromRgb24(rgb);
@ -497,7 +498,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{ {
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++) for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
{ {
int index = Unsafe.Add(ref indicesRowRef, x - descriptorLeft); int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, x - descriptorLeft), 0, colorTableMaxIdx);
if (transIndex != index) if (transIndex != index)
{ {
ref TPixel pixel = ref Unsafe.Add(ref rowRef, x); ref TPixel pixel = ref Unsafe.Add(ref rowRef, x);
@ -621,10 +622,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3; int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3;
this.gifMetadata.GlobalColorTableLength = globalColorTableLength; this.gifMetadata.GlobalColorTableLength = globalColorTableLength;
this.globalColorTable = this.MemoryAllocator.AllocateManagedByteBuffer(globalColorTableLength, AllocationOptions.Clean); this.globalColorTable = this.MemoryAllocator.Allocate<byte>(globalColorTableLength, AllocationOptions.Clean);
// Read the global color table data from the stream // Read the global color table data from the stream
stream.Read(this.globalColorTable.Array, 0, globalColorTableLength); stream.Read(this.globalColorTable.GetSpan());
} }
} }
} }

28
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -7,7 +7,6 @@ using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
@ -54,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary> /// <summary>
/// The pixel sampling strategy for global quantization. /// The pixel sampling strategy for global quantization.
/// </summary> /// </summary>
private IPixelSamplingStrategy pixelSamplingStrategy; private readonly IPixelSamplingStrategy pixelSamplingStrategy;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class. /// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
@ -150,8 +149,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
// The palette quantizer can reuse the same pixel map across multiple frames // The palette quantizer can reuse the same pixel map across multiple frames
// since the palette is unchanging. This allows a reduction of memory usage across // since the palette is unchanging. This allows a reduction of memory usage across
// multi frame gifs using a global palette. // multi frame gifs using a global palette.
EuclideanPixelMap<TPixel> pixelMap = default; PaletteQuantizer<TPixel> paletteFrameQuantizer = default;
bool pixelMapSet = false; bool quantizerInitialized = false;
for (int i = 0; i < image.Frames.Count; i++) for (int i = 0; i < image.Frames.Count; i++)
{ {
ImageFrame<TPixel> frame = image.Frames[i]; ImageFrame<TPixel> frame = image.Frames[i];
@ -166,17 +165,18 @@ namespace SixLabors.ImageSharp.Formats.Gif
} }
else else
{ {
if (!pixelMapSet) if (!quantizerInitialized)
{ {
pixelMapSet = true; quantizerInitialized = true;
pixelMap = new EuclideanPixelMap<TPixel>(this.configuration, quantized.Palette); paletteFrameQuantizer = new PaletteQuantizer<TPixel>(this.configuration, this.quantizer.Options, quantized.Palette);
} }
using var paletteFrameQuantizer = new PaletteQuantizer<TPixel>(this.configuration, this.quantizer.Options, pixelMap);
using IndexedImageFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()); using IndexedImageFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds());
this.WriteImageData(paletteQuantized, stream); this.WriteImageData(paletteQuantized, stream);
} }
} }
paletteFrameQuantizer.Dispose();
} }
private void EncodeLocal<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, Stream stream) private void EncodeLocal<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, Stream stream)
@ -305,7 +305,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
} }
else else
{ {
ratio = (byte)(((1 / vr) * 64) - 15); ratio = (byte)((1 / vr * 64) - 15);
} }
} }
} }
@ -349,7 +349,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
return; return;
} }
for (var i = 0; i < metadata.Comments.Count; i++) for (int i = 0; i < metadata.Comments.Count; i++)
{ {
string comment = metadata.Comments[i]; string comment = metadata.Comments[i];
this.buffer[0] = GifConstants.ExtensionIntroducer; this.buffer[0] = GifConstants.ExtensionIntroducer;
@ -470,14 +470,16 @@ namespace SixLabors.ImageSharp.Formats.Gif
// The maximum number of colors for the bit depth // The maximum number of colors for the bit depth
int colorTableLength = ColorNumerics.GetColorCountForBitDepth(this.bitDepth) * Unsafe.SizeOf<Rgb24>(); int colorTableLength = ColorNumerics.GetColorCountForBitDepth(this.bitDepth) * Unsafe.SizeOf<Rgb24>();
using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength, AllocationOptions.Clean); using IMemoryOwner<byte> colorTable = this.memoryAllocator.Allocate<byte>(colorTableLength, AllocationOptions.Clean);
Span<byte> colorTableSpan = colorTable.GetSpan();
PixelOperations<TPixel>.Instance.ToRgb24Bytes( PixelOperations<TPixel>.Instance.ToRgb24Bytes(
this.configuration, this.configuration,
image.Palette.Span, image.Palette.Span,
colorTable.GetSpan(), colorTableSpan,
image.Palette.Length); image.Palette.Length);
stream.Write(colorTable.Array, 0, colorTableLength); stream.Write(colorTableSpan);
} }
/// <summary> /// <summary>

1
src/ImageSharp/Formats/Gif/LzwEncoder.cs

@ -6,7 +6,6 @@ using System.Buffers;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Gif namespace SixLabors.ImageSharp.Formats.Gif

210
src/ImageSharp/Formats/ImageExtensions.Save.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.
// <auto-generated /> // <auto-generated />
@ -12,6 +12,8 @@ using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Formats.Tiff;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp
{ {
@ -535,5 +537,211 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance), encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance),
cancellationToken); cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsWebp(this Image source, string path) => SaveAsWebp(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsWebpAsync(this Image source, string path) => SaveAsWebpAsync(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsWebpAsync(this Image source, string path, CancellationToken cancellationToken)
=> SaveAsWebpAsync(source, path, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsWebp(this Image source, string path, WebpEncoder encoder) =>
source.Save(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsWebpAsync(this Image source, string path, WebpEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance),
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsWebp(this Image source, Stream stream)
=> SaveAsWebp(source, stream, null);
/// <summary>
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsWebpAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
=> SaveAsWebpAsync(source, stream, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static void SaveAsWebp(this Image source, Stream stream, WebpEncoder encoder)
=> source.Save(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsWebpAsync(this Image source, Stream stream, WebpEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance),
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsTiff(this Image source, string path) => SaveAsTiff(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTiffAsync(this Image source, string path) => SaveAsTiffAsync(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTiffAsync(this Image source, string path, CancellationToken cancellationToken)
=> SaveAsTiffAsync(source, path, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsTiff(this Image source, string path, TiffEncoder encoder) =>
source.Save(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTiffAsync(this Image source, string path, TiffEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance),
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsTiff(this Image source, Stream stream)
=> SaveAsTiff(source, stream, null);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTiffAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
=> SaveAsTiffAsync(source, stream, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static void SaveAsTiff(this Image source, Stream stream, TiffEncoder encoder)
=> source.Save(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTiffAsync(this Image source, Stream stream, TiffEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance),
cancellationToken);
} }
} }

4
src/ImageSharp/Formats/ImageExtensions.Save.tt

@ -1,4 +1,4 @@
<#@ template language="C#" #> <#@ template language="C#" #>
<#@ import namespace="System.Text" #> <#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.Collections.Generic" #>
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
@ -17,6 +17,8 @@ using SixLabors.ImageSharp.Advanced;
"Jpeg", "Jpeg",
"Png", "Png",
"Tga", "Tga",
"Webp",
"Tiff",
}; };
foreach (string fmt in formats) foreach (string fmt in formats)

193
src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs

@ -2,17 +2,22 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Diagnostics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
using System.Text; using System.Text;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
/// <summary> /// <summary>
/// Represents a Jpeg block with <see cref="short"/> coefficients. /// 8x8 matrix of <see cref="short"/> coefficients.
/// </summary> /// </summary>
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
[StructLayout(LayoutKind.Explicit)]
internal unsafe struct Block8x8 : IEquatable<Block8x8> internal unsafe struct Block8x8 : IEquatable<Block8x8>
{ {
/// <summary> /// <summary>
@ -20,24 +25,44 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary> /// </summary>
public const int Size = 64; public const int Size = 64;
#pragma warning disable IDE0051 // Remove unused private member
/// <summary> /// <summary>
/// A fixed size buffer holding the values. /// A placeholder buffer so the actual struct occupies exactly 64 * 2 bytes.
/// See: <see>
/// <cref>https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/unsafe-code-pointers/fixed-size-buffers</cref>
/// </see>
/// </summary> /// </summary>
/// <remarks>
/// This is not used directly in the code.
/// </remarks>
[FieldOffset(0)]
private fixed short data[Size]; private fixed short data[Size];
#pragma warning restore IDE0051
/// <summary>
/// Initializes a new instance of the <see cref="Block8x8"/> struct. #if SUPPORTS_RUNTIME_INTRINSICS
/// </summary> [FieldOffset(0)]
/// <param name="coefficients">A <see cref="Span{T}"/> of coefficients</param> public Vector128<short> V0;
public Block8x8(Span<short> coefficients) [FieldOffset(16)]
{ public Vector128<short> V1;
ref byte selfRef = ref Unsafe.As<Block8x8, byte>(ref this); [FieldOffset(32)]
ref byte sourceRef = ref Unsafe.As<short, byte>(ref MemoryMarshal.GetReference(coefficients)); public Vector128<short> V2;
Unsafe.CopyBlock(ref selfRef, ref sourceRef, Size * sizeof(short)); [FieldOffset(48)]
} public Vector128<short> V3;
[FieldOffset(64)]
public Vector128<short> V4;
[FieldOffset(80)]
public Vector128<short> V5;
[FieldOffset(96)]
public Vector128<short> V6;
[FieldOffset(112)]
public Vector128<short> V7;
[FieldOffset(0)]
public Vector256<short> V01;
[FieldOffset(32)]
public Vector256<short> V23;
[FieldOffset(64)]
public Vector256<short> V45;
[FieldOffset(96)]
public Vector256<short> V67;
#endif
/// <summary> /// <summary>
/// Gets or sets a <see cref="short"/> value at the given index /// Gets or sets a <see cref="short"/> value at the given index
@ -49,7 +74,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
GuardBlockIndex(idx); DebugGuard.MustBeBetweenOrEqualTo(idx, 0, Size - 1, nameof(idx));
ref short selfRef = ref Unsafe.As<Block8x8, short>(ref this); ref short selfRef = ref Unsafe.As<Block8x8, short>(ref this);
return Unsafe.Add(ref selfRef, idx); return Unsafe.Add(ref selfRef, idx);
} }
@ -57,7 +83,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
set set
{ {
GuardBlockIndex(idx); DebugGuard.MustBeBetweenOrEqualTo(idx, 0, Size - 1, nameof(idx));
ref short selfRef = ref Unsafe.As<Block8x8, short>(ref this); ref short selfRef = ref Unsafe.As<Block8x8, short>(ref this);
Unsafe.Add(ref selfRef, idx) = value; Unsafe.Add(ref selfRef, idx) = value;
} }
@ -75,15 +102,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
set => this[(y * 8) + x] = value; set => this[(y * 8) + x] = value;
} }
public static bool operator ==(Block8x8 left, Block8x8 right) public static bool operator ==(Block8x8 left, Block8x8 right) => left.Equals(right);
{
return left.Equals(right);
}
public static bool operator !=(Block8x8 left, Block8x8 right) public static bool operator !=(Block8x8 left, Block8x8 right) => !left.Equals(right);
{
return !left.Equals(right);
}
/// <summary> /// <summary>
/// Multiply all elements by a given <see cref="int"/> /// Multiply all elements by a given <see cref="int"/>
@ -149,34 +170,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
return result; return result;
} }
/// <summary> public static Block8x8 Load(Span<short> data)
/// Pointer-based "Indexer" (getter part)
/// </summary>
/// <param name="blockPtr">Block pointer</param>
/// <param name="idx">Index</param>
/// <returns>The scaleVec value at the specified index</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static short GetScalarAt(Block8x8* blockPtr, int idx)
{
GuardBlockIndex(idx);
short* fp = blockPtr->data;
return fp[idx];
}
/// <summary>
/// Pointer-based "Indexer" (setter part)
/// </summary>
/// <param name="blockPtr">Block pointer</param>
/// <param name="idx">Index</param>
/// <param name="value">Value</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetScalarAt(Block8x8* blockPtr, int idx, short value)
{ {
GuardBlockIndex(idx); Unsafe.SkipInit(out Block8x8 result);
result.LoadFrom(data);
short* fp = blockPtr->data; return result;
fp[idx] = value;
} }
/// <summary> /// <summary>
@ -194,7 +192,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary> /// </summary>
public short[] ToArray() public short[] ToArray()
{ {
var result = new short[Size]; short[] result = new short[Size];
this.CopyTo(result); this.CopyTo(result);
return result; return result;
} }
@ -206,7 +204,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
ref byte selfRef = ref Unsafe.As<Block8x8, byte>(ref this); ref byte selfRef = ref Unsafe.As<Block8x8, byte>(ref this);
ref byte destRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<short, byte>(destination)); ref byte destRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<short, byte>(destination));
Unsafe.CopyBlock(ref destRef, ref selfRef, Size * sizeof(short)); Unsafe.CopyBlockUnaligned(ref destRef, ref selfRef, Size * sizeof(short));
} }
/// <summary> /// <summary>
@ -220,6 +218,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
} }
} }
/// <summary>
/// Load raw 16bit integers from source.
/// </summary>
/// <param name="source">Source</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void LoadFrom(Span<short> source)
{
ref byte sourceRef = ref Unsafe.As<short, byte>(ref MemoryMarshal.GetReference(source));
ref byte destRef = ref Unsafe.As<Block8x8, byte>(ref this);
Unsafe.CopyBlockUnaligned(ref destRef, ref sourceRef, Size * sizeof(short));
}
/// <summary> /// <summary>
/// Cast and copy <see cref="Size"/> <see cref="int"/>-s from the beginning of 'source' span. /// Cast and copy <see cref="Size"/> <see cref="int"/>-s from the beginning of 'source' span.
/// </summary> /// </summary>
@ -231,13 +242,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
} }
} }
[Conditional("DEBUG")]
private static void GuardBlockIndex(int idx)
{
DebugGuard.MustBeLessThan(idx, Size, nameof(idx));
DebugGuard.MustBeGreaterThanOrEqualTo(idx, 0, nameof(idx));
}
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString()
{ {
@ -271,15 +275,66 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool Equals(object obj) public override bool Equals(object obj) => obj is Block8x8 other && this.Equals(other);
{
return obj is Block8x8 other && this.Equals(other);
}
/// <inheritdoc /> /// <inheritdoc />
public override int GetHashCode() public override int GetHashCode() => (this[0] * 31) + this[1];
/// <summary>
/// Returns index of the last non-zero element in given matrix.
/// </summary>
/// <returns>
/// Index of the last non-zero element. Returns -1 if all elements are equal to zero.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
public nint GetLastNonZeroIndex()
{ {
return (this[0] * 31) + this[1]; #if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111);
Vector256<short> zero16 = Vector256<short>.Zero;
ref Vector256<short> mcuStride = ref Unsafe.As<Block8x8, Vector256<short>>(ref this);
for (nint i = 3; i >= 0; i--)
{
int areEqual = Avx2.MoveMask(Avx2.CompareEqual(Unsafe.Add(ref mcuStride, i), zero16).AsByte());
if (areEqual != equalityMask)
{
// Each 2 bits represents comparison operation for each 2-byte element in input vectors
// LSB represents first element in the stride
// MSB represents last element in the stride
// lzcnt operation would calculate number of zero numbers at the end
// Given mask is not actually suitable for lzcnt as 1's represent zero elements and 0's represent non-zero elements
// So we need to invert it
int lzcnt = BitOperations.LeadingZeroCount(~(uint)areEqual);
// As input number is represented by 2 bits in the mask, we need to divide lzcnt result by 2
// to get the exact number of zero elements in the stride
int strideRelativeIndex = 15 - (lzcnt / 2);
return (i * 16) + strideRelativeIndex;
}
}
return -1;
}
else
#endif
{
nint index = Size - 1;
ref short elemRef = ref Unsafe.As<Block8x8, short>(ref this);
while (index >= 0 && Unsafe.Add(ref elemRef, index) == 0)
{
index--;
}
return index;
}
} }
/// <summary> /// <summary>

149
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Intrinsic.cs

@ -0,0 +1,149 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
#if SUPPORTS_RUNTIME_INTRINSICS
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal partial struct Block8x8F
{
/// <summary>
/// A number of rows of 8 scalar coefficients each in <see cref="Block8x8F"/>
/// </summary>
public const int RowCount = 8;
[FieldOffset(0)]
public Vector256<float> V0;
[FieldOffset(32)]
public Vector256<float> V1;
[FieldOffset(64)]
public Vector256<float> V2;
[FieldOffset(96)]
public Vector256<float> V3;
[FieldOffset(128)]
public Vector256<float> V4;
[FieldOffset(160)]
public Vector256<float> V5;
[FieldOffset(192)]
public Vector256<float> V6;
[FieldOffset(224)]
public Vector256<float> V7;
private static readonly Vector256<int> MultiplyIntoInt16ShuffleMask = Vector256.Create(0, 1, 4, 5, 2, 3, 6, 7);
private static unsafe void MultiplyIntoInt16_Avx2(ref Block8x8F a, ref Block8x8F b, ref Block8x8 dest)
{
DebugGuard.IsTrue(Avx2.IsSupported, "Avx2 support is required to run this operation!");
ref Vector256<float> aBase = ref a.V0;
ref Vector256<float> bBase = ref b.V0;
ref Vector256<short> destRef = ref dest.V01;
for (nint i = 0; i < 8; i += 2)
{
Vector256<int> row0 = Avx.ConvertToVector256Int32(Avx.Multiply(Unsafe.Add(ref aBase, i + 0), Unsafe.Add(ref bBase, i + 0)));
Vector256<int> row1 = Avx.ConvertToVector256Int32(Avx.Multiply(Unsafe.Add(ref aBase, i + 1), Unsafe.Add(ref bBase, i + 1)));
Vector256<short> row = Avx2.PackSignedSaturate(row0, row1);
row = Avx2.PermuteVar8x32(row.AsInt32(), MultiplyIntoInt16ShuffleMask).AsInt16();
Unsafe.Add(ref destRef, (IntPtr)((uint)i / 2)) = row;
}
}
private static void MultiplyIntoInt16_Sse2(ref Block8x8F a, ref Block8x8F b, ref Block8x8 dest)
{
DebugGuard.IsTrue(Sse2.IsSupported, "Sse2 support is required to run this operation!");
ref Vector128<float> aBase = ref Unsafe.As<Block8x8F, Vector128<float>>(ref a);
ref Vector128<float> bBase = ref Unsafe.As<Block8x8F, Vector128<float>>(ref b);
ref Vector128<short> destBase = ref Unsafe.As<Block8x8, Vector128<short>>(ref dest);
for (int i = 0; i < 16; i += 2)
{
Vector128<int> left = Sse2.ConvertToVector128Int32(Sse.Multiply(Unsafe.Add(ref aBase, i + 0), Unsafe.Add(ref bBase, i + 0)));
Vector128<int> right = Sse2.ConvertToVector128Int32(Sse.Multiply(Unsafe.Add(ref aBase, i + 1), Unsafe.Add(ref bBase, i + 1)));
Vector128<short> row = Sse2.PackSignedSaturate(left, right);
Unsafe.Add(ref destBase, (IntPtr)((uint)i / 2)) = row;
}
}
private void TransposeInplace_Avx()
{
// https://stackoverflow.com/questions/25622745/transpose-an-8x8-float-using-avx-avx2/25627536#25627536
Vector256<float> r0 = Avx.InsertVector128(
this.V0,
Unsafe.As<Vector4, Vector128<float>>(ref this.V4L),
1);
Vector256<float> r1 = Avx.InsertVector128(
this.V1,
Unsafe.As<Vector4, Vector128<float>>(ref this.V5L),
1);
Vector256<float> r2 = Avx.InsertVector128(
this.V2,
Unsafe.As<Vector4, Vector128<float>>(ref this.V6L),
1);
Vector256<float> r3 = Avx.InsertVector128(
this.V3,
Unsafe.As<Vector4, Vector128<float>>(ref this.V7L),
1);
Vector256<float> r4 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V0R).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V4R),
1);
Vector256<float> r5 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V1R).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V5R),
1);
Vector256<float> r6 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V2R).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V6R),
1);
Vector256<float> r7 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V3R).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V7R),
1);
Vector256<float> t0 = Avx.UnpackLow(r0, r1);
Vector256<float> t2 = Avx.UnpackLow(r2, r3);
Vector256<float> v = Avx.Shuffle(t0, t2, 0x4E);
this.V0 = Avx.Blend(t0, v, 0xCC);
this.V1 = Avx.Blend(t2, v, 0x33);
Vector256<float> t4 = Avx.UnpackLow(r4, r5);
Vector256<float> t6 = Avx.UnpackLow(r6, r7);
v = Avx.Shuffle(t4, t6, 0x4E);
this.V4 = Avx.Blend(t4, v, 0xCC);
this.V5 = Avx.Blend(t6, v, 0x33);
Vector256<float> t1 = Avx.UnpackHigh(r0, r1);
Vector256<float> t3 = Avx.UnpackHigh(r2, r3);
v = Avx.Shuffle(t1, t3, 0x4E);
this.V2 = Avx.Blend(t1, v, 0xCC);
this.V3 = Avx.Blend(t3, v, 0x33);
Vector256<float> t5 = Avx.UnpackHigh(r4, r5);
Vector256<float> t7 = Avx.UnpackHigh(r6, r7);
v = Avx.Shuffle(t5, t7, 0x4E);
this.V6 = Avx.Blend(t5, v, 0xCC);
this.V7 = Avx.Blend(t7, v, 0x33);
}
}
}
#endif

2
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.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.
using System.Numerics; using System.Numerics;

560
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs

@ -16,9 +16,9 @@ using System.Text;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
/// <summary> /// <summary>
/// Represents a Jpeg block with <see cref="float"/> coefficients. /// 8x8 matrix of <see cref="float"/> coefficients.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Explicit)]
internal partial struct Block8x8F : IEquatable<Block8x8F> internal partial struct Block8x8F : IEquatable<Block8x8F>
{ {
/// <summary> /// <summary>
@ -27,28 +27,44 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
public const int Size = 64; public const int Size = 64;
#pragma warning disable SA1600 // ElementsMustBeDocumented #pragma warning disable SA1600 // ElementsMustBeDocumented
[FieldOffset(0)]
public Vector4 V0L; public Vector4 V0L;
[FieldOffset(16)]
public Vector4 V0R; public Vector4 V0R;
[FieldOffset(32)]
public Vector4 V1L; public Vector4 V1L;
[FieldOffset(48)]
public Vector4 V1R; public Vector4 V1R;
[FieldOffset(64)]
public Vector4 V2L; public Vector4 V2L;
[FieldOffset(80)]
public Vector4 V2R; public Vector4 V2R;
[FieldOffset(96)]
public Vector4 V3L; public Vector4 V3L;
[FieldOffset(112)]
public Vector4 V3R; public Vector4 V3R;
[FieldOffset(128)]
public Vector4 V4L; public Vector4 V4L;
[FieldOffset(144)]
public Vector4 V4R; public Vector4 V4R;
[FieldOffset(160)]
public Vector4 V5L; public Vector4 V5L;
[FieldOffset(176)]
public Vector4 V5R; public Vector4 V5R;
[FieldOffset(192)]
public Vector4 V6L; public Vector4 V6L;
[FieldOffset(208)]
public Vector4 V6R; public Vector4 V6R;
[FieldOffset(224)]
public Vector4 V7L; public Vector4 V7L;
[FieldOffset(240)]
public Vector4 V7R; public Vector4 V7R;
#pragma warning restore SA1600 // ElementsMustBeDocumented #pragma warning restore SA1600 // ElementsMustBeDocumented
@ -62,17 +78,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
GuardBlockIndex(idx); DebugGuard.MustBeBetweenOrEqualTo(idx, 0, Size - 1, nameof(idx));
ref float selfRef = ref Unsafe.As<Block8x8F, float>(ref this); ref float selfRef = ref Unsafe.As<Block8x8F, float>(ref this);
return Unsafe.Add(ref selfRef, idx); return Unsafe.Add(ref selfRef, (nint)(uint)idx);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
set set
{ {
GuardBlockIndex(idx); DebugGuard.MustBeBetweenOrEqualTo(idx, 0, Size - 1, nameof(idx));
ref float selfRef = ref Unsafe.As<Block8x8F, float>(ref this); ref float selfRef = ref Unsafe.As<Block8x8F, float>(ref this);
Unsafe.Add(ref selfRef, idx) = value; Unsafe.Add(ref selfRef, (nint)(uint)idx) = value;
} }
} }
@ -148,13 +164,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
return result; return result;
} }
/// <summary>
/// Fill the block with defaults (zeroes).
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void Clear()
=> this = default; // The cheapest way to do this in C#:
/// <summary> /// <summary>
/// Load raw 32bit floating point data from source. /// Load raw 32bit floating point data from source.
/// </summary> /// </summary>
@ -262,7 +271,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
public float[] ToArray() public float[] ToArray()
{ {
var result = new float[Size]; float[] result = new float[Size];
this.ScaledCopyTo(result); this.ScaledCopyTo(result);
return result; return result;
} }
@ -278,14 +287,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
if (Avx.IsSupported) if (Avx.IsSupported)
{ {
var valueVec = Vector256.Create(value); var valueVec = Vector256.Create(value);
Unsafe.As<Vector4, Vector256<float>>(ref this.V0L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V0L), valueVec); this.V0 = Avx.Multiply(this.V0, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V1L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V1L), valueVec); this.V1 = Avx.Multiply(this.V1, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V2L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V2L), valueVec); this.V2 = Avx.Multiply(this.V2, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V3L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V3L), valueVec); this.V3 = Avx.Multiply(this.V3, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V4L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V4L), valueVec); this.V4 = Avx.Multiply(this.V4, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V5L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V5L), valueVec); this.V5 = Avx.Multiply(this.V5, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V6L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V6L), valueVec); this.V6 = Avx.Multiply(this.V6, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V7L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V7L), valueVec); this.V7 = Avx.Multiply(this.V7, valueVec);
} }
else else
#endif #endif
@ -319,45 +328,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
if (Avx.IsSupported) if (Avx.IsSupported)
{ {
Unsafe.As<Vector4, Vector256<float>>(ref this.V0L) this.V0 = Avx.Multiply(this.V0, other.V0);
= Avx.Multiply( this.V1 = Avx.Multiply(this.V1, other.V1);
Unsafe.As<Vector4, Vector256<float>>(ref this.V0L), this.V2 = Avx.Multiply(this.V2, other.V2);
Unsafe.As<Vector4, Vector256<float>>(ref other.V0L)); this.V3 = Avx.Multiply(this.V3, other.V3);
this.V4 = Avx.Multiply(this.V4, other.V4);
Unsafe.As<Vector4, Vector256<float>>(ref this.V1L) this.V5 = Avx.Multiply(this.V5, other.V5);
= Avx.Multiply( this.V6 = Avx.Multiply(this.V6, other.V6);
Unsafe.As<Vector4, Vector256<float>>(ref this.V1L), this.V7 = Avx.Multiply(this.V7, other.V7);
Unsafe.As<Vector4, Vector256<float>>(ref other.V1L));
Unsafe.As<Vector4, Vector256<float>>(ref this.V2L)
= Avx.Multiply(
Unsafe.As<Vector4, Vector256<float>>(ref this.V2L),
Unsafe.As<Vector4, Vector256<float>>(ref other.V2L));
Unsafe.As<Vector4, Vector256<float>>(ref this.V3L)
= Avx.Multiply(
Unsafe.As<Vector4, Vector256<float>>(ref this.V3L),
Unsafe.As<Vector4, Vector256<float>>(ref other.V3L));
Unsafe.As<Vector4, Vector256<float>>(ref this.V4L)
= Avx.Multiply(
Unsafe.As<Vector4, Vector256<float>>(ref this.V4L),
Unsafe.As<Vector4, Vector256<float>>(ref other.V4L));
Unsafe.As<Vector4, Vector256<float>>(ref this.V5L)
= Avx.Multiply(
Unsafe.As<Vector4, Vector256<float>>(ref this.V5L),
Unsafe.As<Vector4, Vector256<float>>(ref other.V5L));
Unsafe.As<Vector4, Vector256<float>>(ref this.V6L)
= Avx.Multiply(
Unsafe.As<Vector4, Vector256<float>>(ref this.V6L),
Unsafe.As<Vector4, Vector256<float>>(ref other.V6L));
Unsafe.As<Vector4, Vector256<float>>(ref this.V7L)
= Avx.Multiply(
Unsafe.As<Vector4, Vector256<float>>(ref this.V7L),
Unsafe.As<Vector4, Vector256<float>>(ref other.V7L));
} }
else else
#endif #endif
@ -392,14 +370,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
if (Avx.IsSupported) if (Avx.IsSupported)
{ {
var valueVec = Vector256.Create(value); var valueVec = Vector256.Create(value);
Unsafe.As<Vector4, Vector256<float>>(ref this.V0L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V0L), valueVec); this.V0 = Avx.Add(this.V0, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V1L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V1L), valueVec); this.V1 = Avx.Add(this.V1, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V2L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V2L), valueVec); this.V2 = Avx.Add(this.V2, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V3L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V3L), valueVec); this.V3 = Avx.Add(this.V3, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V4L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V4L), valueVec); this.V4 = Avx.Add(this.V4, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V5L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V5L), valueVec); this.V5 = Avx.Add(this.V5, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V6L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V6L), valueVec); this.V6 = Avx.Add(this.V6, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V7L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V7L), valueVec); this.V7 = Avx.Add(this.V7, valueVec);
} }
else else
#endif #endif
@ -425,183 +403,37 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
} }
/// <summary> /// <summary>
/// Quantize the block. /// Quantize input block, apply zig-zag ordering and store result as 16bit integers.
/// </summary>
/// <param name="blockPtr">The block pointer.</param>
/// <param name="qtPtr">The qt pointer.</param>
/// <param name="unzigPtr">Unzig pointer</param>
public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, byte* unzigPtr)
{
float* b = (float*)blockPtr;
float* qtp = (float*)qtPtr;
for (int qtIndex = 0; qtIndex < Size; qtIndex++)
{
byte blockIndex = unzigPtr[qtIndex];
float* unzigPos = b + blockIndex;
float val = *unzigPos;
val *= qtp[qtIndex];
*unzigPos = val;
}
}
/// <summary>
/// Quantize 'block' into 'dest' using the 'qt' quantization table:
/// Unzig the elements of block into dest, while dividing them by elements of qt and "pre-rounding" the values.
/// To finish the rounding it's enough to (int)-cast these values.
/// </summary>
/// <param name="block">Source block</param>
/// <param name="dest">Destination block</param>
/// <param name="qt">The quantization table</param>
/// <param name="unZig">The 8x8 Unzig block.</param>
public static unsafe void Quantize(
ref Block8x8F block,
ref Block8x8F dest,
ref Block8x8F qt,
ref ZigZag unZig)
{
for (int zig = 0; zig < Size; zig++)
{
dest[zig] = block[unZig[zig]];
}
DivideRoundAll(ref dest, ref qt);
}
/// <summary>
/// Scales the 16x16 region represented by the 4 source blocks to the 8x8 DST block.
/// </summary> /// </summary>
/// <param name="destination">The destination block.</param> /// <param name="block">Source block.</param>
/// <param name="source">The source block.</param> /// <param name="dest">Destination block.</param>
public static unsafe void Scale16X16To8X8(ref Block8x8F destination, ReadOnlySpan<Block8x8F> source) /// <param name="qt">The quantization table.</param>
public static void Quantize(ref Block8x8F block, ref Block8x8 dest, ref Block8x8F qt)
{ {
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported) if (Avx2.IsSupported)
{ {
Scale16X16To8X8Vectorized(ref destination, source); MultiplyIntoInt16_Avx2(ref block, ref qt, ref dest);
return; ZigZag.ApplyZigZagOrderingAvx2(ref dest);
} }
#endif else if (Ssse3.IsSupported)
Scale16X16To8X8Scalar(ref destination, source);
}
private static void Scale16X16To8X8Vectorized(ref Block8x8F destination, ReadOnlySpan<Block8x8F> source)
{
#if SUPPORTS_RUNTIME_INTRINSICS
Debug.Assert(Avx2.IsSupported, "AVX2 is required to execute this method");
var f2 = Vector256.Create(2f);
var f025 = Vector256.Create(0.25f);
Vector256<int> switchInnerDoubleWords = Unsafe.As<byte, Vector256<int>>(ref MemoryMarshal.GetReference(SimdUtils.HwIntrinsics.PermuteMaskSwitchInnerDWords8x32));
ref Vector256<float> destRef = ref Unsafe.As<Block8x8F, Vector256<float>>(ref destination);
for (int i = 0; i < 2; i++)
{ {
ref Vector256<float> in1 = ref Unsafe.As<Block8x8F, Vector256<float>>(ref Unsafe.Add(ref MemoryMarshal.GetReference(source), 2 * i)); MultiplyIntoInt16_Sse2(ref block, ref qt, ref dest);
ref Vector256<float> in2 = ref Unsafe.As<Block8x8F, Vector256<float>>(ref Unsafe.Add(ref MemoryMarshal.GetReference(source), (2 * i) + 1)); ZigZag.ApplyZigZagOrderingSsse3(ref dest);
for (int j = 0; j < 8; j += 2)
{
Vector256<float> a = Unsafe.Add(ref in1, j);
Vector256<float> b = Unsafe.Add(ref in1, j + 1);
Vector256<float> c = Unsafe.Add(ref in2, j);
Vector256<float> d = Unsafe.Add(ref in2, j + 1);
Vector256<float> calc1 = Avx.Shuffle(a, c, 0b10_00_10_00);
Vector256<float> calc2 = Avx.Shuffle(a, c, 0b11_01_11_01);
Vector256<float> calc3 = Avx.Shuffle(b, d, 0b10_00_10_00);
Vector256<float> calc4 = Avx.Shuffle(b, d, 0b11_01_11_01);
Vector256<float> sum = Avx.Add(Avx.Add(calc1, calc2), Avx.Add(calc3, calc4));
Vector256<float> add = Avx.Add(sum, f2);
Vector256<float> res = Avx.Multiply(add, f025);
destRef = Avx2.PermuteVar8x32(res, switchInnerDoubleWords);
destRef = ref Unsafe.Add(ref destRef, 1);
}
} }
else
#endif #endif
}
private static unsafe void Scale16X16To8X8Scalar(ref Block8x8F destination, ReadOnlySpan<Block8x8F> source)
{
for (int i = 0; i < 4; i++)
{ {
int dstOff = ((i & 2) << 4) | ((i & 1) << 2); for (int i = 0; i < Size; i++)
Block8x8F iSource = source[i];
for (int y = 0; y < 4; y++)
{
for (int x = 0; x < 4; x++)
{
int j = (16 * y) + (2 * x);
float sum = iSource[j] + iSource[j + 1] + iSource[j + 8] + iSource[j + 9];
destination[(8 * y) + x + dstOff] = (sum + 2) * .25F;
}
}
}
}
[MethodImpl(InliningOptions.ShortMethod)]
private static void DivideRoundAll(ref Block8x8F a, ref Block8x8F b)
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx.IsSupported)
{
var vnegOne = Vector256.Create(-1f);
var vadd = Vector256.Create(.5F);
var vone = Vector256.Create(1f);
ref Vector256<float> aBase = ref Unsafe.AsRef(Unsafe.As<Vector4, Vector256<float>>(ref a.V0L));
ref Vector256<float> bBase = ref Unsafe.AsRef(Unsafe.As<Vector4, Vector256<float>>(ref b.V0L));
ref Vector256<float> aEnd = ref Unsafe.Add(ref aBase, 8);
do
{ {
Vector256<float> voff = Avx.Multiply(Avx.Min(Avx.Max(vnegOne, aBase), vone), vadd); int idx = ZigZag.ZigZagOrder[i];
Unsafe.Add(ref aBase, 0) = Avx.Add(Avx.Divide(aBase, bBase), voff); float quantizedVal = block[idx] * qt[idx];
quantizedVal += quantizedVal < 0 ? -0.5f : 0.5f;
aBase = ref Unsafe.Add(ref aBase, 1); dest[i] = (short)quantizedVal;
bBase = ref Unsafe.Add(ref bBase, 1);
} }
while (Unsafe.IsAddressLessThan(ref aBase, ref aEnd));
}
else
#endif
{
a.V0L = DivideRound(a.V0L, b.V0L);
a.V0R = DivideRound(a.V0R, b.V0R);
a.V1L = DivideRound(a.V1L, b.V1L);
a.V1R = DivideRound(a.V1R, b.V1R);
a.V2L = DivideRound(a.V2L, b.V2L);
a.V2R = DivideRound(a.V2R, b.V2R);
a.V3L = DivideRound(a.V3L, b.V3L);
a.V3R = DivideRound(a.V3R, b.V3R);
a.V4L = DivideRound(a.V4L, b.V4L);
a.V4R = DivideRound(a.V4R, b.V4R);
a.V5L = DivideRound(a.V5L, b.V5L);
a.V5R = DivideRound(a.V5R, b.V5R);
a.V6L = DivideRound(a.V6L, b.V6L);
a.V6R = DivideRound(a.V6R, b.V6R);
a.V7L = DivideRound(a.V7L, b.V7L);
a.V7R = DivideRound(a.V7R, b.V7R);
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor)
{
var neg = new Vector4(-1);
var add = new Vector4(.5F);
// sign(dividend) = max(min(dividend, 1), -1)
Vector4 sign = Numerics.Clamp(dividend, neg, Vector4.One);
// AlmostRound(dividend/divisor) = dividend/divisor + 0.5*sign(dividend)
return (dividend / divisor) + (sign * add);
}
public void RoundInto(ref Block8x8 dest) public void RoundInto(ref Block8x8 dest)
{ {
for (int i = 0; i < Size; i++) for (int i = 0; i < Size; i++)
@ -699,6 +531,47 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
Unsafe.Add(ref dRef, 7) = bottom; Unsafe.Add(ref dRef, 7) = bottom;
} }
/// <summary>
/// Compares entire 8x8 block to a single scalar value.
/// </summary>
/// <param name="value">Value to compare to.</param>
public bool EqualsToScalar(int value)
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111);
var targetVector = Vector256.Create(value);
ref Vector256<float> blockStride = ref this.V0;
for (int i = 0; i < RowCount; i++)
{
Vector256<int> areEqual = Avx2.CompareEqual(Avx.ConvertToVector256Int32WithTruncation(Unsafe.Add(ref this.V0, i)), targetVector);
if (Avx2.MoveMask(areEqual.AsByte()) != equalityMask)
{
return false;
}
}
return true;
}
#endif
{
ref float scalars = ref Unsafe.As<Block8x8F, float>(ref this);
for (int i = 0; i < Size; i++)
{
if ((int)Unsafe.Add(ref scalars, i) != value)
{
return false;
}
}
return true;
}
}
/// <inheritdoc /> /// <inheritdoc />
public bool Equals(Block8x8F other) public bool Equals(Block8x8F other)
=> this.V0L == other.V0L => this.V0L == other.V0L
@ -735,172 +608,89 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
return sb.ToString(); return sb.ToString();
} }
[MethodImpl(InliningOptions.ShortMethod)]
private static Vector<float> NormalizeAndRound(Vector<float> row, Vector<float> off, Vector<float> max)
{
row += off;
row = Vector.Max(row, Vector<float>.Zero);
row = Vector.Min(row, max);
return row.FastRound();
}
[Conditional("DEBUG")]
private static void GuardBlockIndex(int idx)
{
DebugGuard.MustBeLessThan(idx, Size, nameof(idx));
DebugGuard.MustBeGreaterThanOrEqualTo(idx, 0, nameof(idx));
}
/// <summary> /// <summary>
/// Transpose the block into the destination block. /// Transpose the block inplace.
/// </summary> /// </summary>
/// <param name="d">The destination block</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void TransposeInto(ref Block8x8F d) public void TransposeInplace()
{ {
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
if (Avx.IsSupported) if (Avx.IsSupported)
{ {
// https://stackoverflow.com/questions/25622745/transpose-an-8x8-float-using-avx-avx2/25627536#25627536 this.TransposeInplace_Avx();
Vector256<float> r0 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V0L).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V4L),
1);
Vector256<float> r1 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V1L).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V5L),
1);
Vector256<float> r2 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V2L).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V6L),
1);
Vector256<float> r3 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V3L).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V7L),
1);
Vector256<float> r4 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V0R).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V4R),
1);
Vector256<float> r5 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V1R).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V5R),
1);
Vector256<float> r6 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V2R).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V6R),
1);
Vector256<float> r7 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V3R).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V7R),
1);
Vector256<float> t0 = Avx.UnpackLow(r0, r1);
Vector256<float> t2 = Avx.UnpackLow(r2, r3);
Vector256<float> v = Avx.Shuffle(t0, t2, 0x4E);
Unsafe.As<Vector4, Vector256<float>>(ref d.V0L) = Avx.Blend(t0, v, 0xCC);
Unsafe.As<Vector4, Vector256<float>>(ref d.V1L) = Avx.Blend(t2, v, 0x33);
Vector256<float> t4 = Avx.UnpackLow(r4, r5);
Vector256<float> t6 = Avx.UnpackLow(r6, r7);
v = Avx.Shuffle(t4, t6, 0x4E);
Unsafe.As<Vector4, Vector256<float>>(ref d.V4L) = Avx.Blend(t4, v, 0xCC);
Unsafe.As<Vector4, Vector256<float>>(ref d.V5L) = Avx.Blend(t6, v, 0x33);
Vector256<float> t1 = Avx.UnpackHigh(r0, r1);
Vector256<float> t3 = Avx.UnpackHigh(r2, r3);
v = Avx.Shuffle(t1, t3, 0x4E);
Unsafe.As<Vector4, Vector256<float>>(ref d.V2L) = Avx.Blend(t1, v, 0xCC);
Unsafe.As<Vector4, Vector256<float>>(ref d.V3L) = Avx.Blend(t3, v, 0x33);
Vector256<float> t5 = Avx.UnpackHigh(r4, r5);
Vector256<float> t7 = Avx.UnpackHigh(r6, r7);
v = Avx.Shuffle(t5, t7, 0x4E);
Unsafe.As<Vector4, Vector256<float>>(ref d.V6L) = Avx.Blend(t5, v, 0xCC);
Unsafe.As<Vector4, Vector256<float>>(ref d.V7L) = Avx.Blend(t7, v, 0x33);
} }
else else
#endif #endif
{ {
d.V0L.X = this.V0L.X; this.TransposeInplace_Scalar();
d.V1L.X = this.V0L.Y;
d.V2L.X = this.V0L.Z;
d.V3L.X = this.V0L.W;
d.V4L.X = this.V0R.X;
d.V5L.X = this.V0R.Y;
d.V6L.X = this.V0R.Z;
d.V7L.X = this.V0R.W;
d.V0L.Y = this.V1L.X;
d.V1L.Y = this.V1L.Y;
d.V2L.Y = this.V1L.Z;
d.V3L.Y = this.V1L.W;
d.V4L.Y = this.V1R.X;
d.V5L.Y = this.V1R.Y;
d.V6L.Y = this.V1R.Z;
d.V7L.Y = this.V1R.W;
d.V0L.Z = this.V2L.X;
d.V1L.Z = this.V2L.Y;
d.V2L.Z = this.V2L.Z;
d.V3L.Z = this.V2L.W;
d.V4L.Z = this.V2R.X;
d.V5L.Z = this.V2R.Y;
d.V6L.Z = this.V2R.Z;
d.V7L.Z = this.V2R.W;
d.V0L.W = this.V3L.X;
d.V1L.W = this.V3L.Y;
d.V2L.W = this.V3L.Z;
d.V3L.W = this.V3L.W;
d.V4L.W = this.V3R.X;
d.V5L.W = this.V3R.Y;
d.V6L.W = this.V3R.Z;
d.V7L.W = this.V3R.W;
d.V0R.X = this.V4L.X;
d.V1R.X = this.V4L.Y;
d.V2R.X = this.V4L.Z;
d.V3R.X = this.V4L.W;
d.V4R.X = this.V4R.X;
d.V5R.X = this.V4R.Y;
d.V6R.X = this.V4R.Z;
d.V7R.X = this.V4R.W;
d.V0R.Y = this.V5L.X;
d.V1R.Y = this.V5L.Y;
d.V2R.Y = this.V5L.Z;
d.V3R.Y = this.V5L.W;
d.V4R.Y = this.V5R.X;
d.V5R.Y = this.V5R.Y;
d.V6R.Y = this.V5R.Z;
d.V7R.Y = this.V5R.W;
d.V0R.Z = this.V6L.X;
d.V1R.Z = this.V6L.Y;
d.V2R.Z = this.V6L.Z;
d.V3R.Z = this.V6L.W;
d.V4R.Z = this.V6R.X;
d.V5R.Z = this.V6R.Y;
d.V6R.Z = this.V6R.Z;
d.V7R.Z = this.V6R.W;
d.V0R.W = this.V7L.X;
d.V1R.W = this.V7L.Y;
d.V2R.W = this.V7L.Z;
d.V3R.W = this.V7L.W;
d.V4R.W = this.V7R.X;
d.V5R.W = this.V7R.Y;
d.V6R.W = this.V7R.Z;
d.V7R.W = this.V7R.W;
} }
} }
/// <summary>
/// Scalar inplace transpose implementation for <see cref="TransposeInplace"/>
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
private void TransposeInplace_Scalar()
{
ref float elemRef = ref Unsafe.As<Block8x8F, float>(ref this);
// row #0
Swap(ref Unsafe.Add(ref elemRef, 1), ref Unsafe.Add(ref elemRef, 8));
Swap(ref Unsafe.Add(ref elemRef, 2), ref Unsafe.Add(ref elemRef, 16));
Swap(ref Unsafe.Add(ref elemRef, 3), ref Unsafe.Add(ref elemRef, 24));
Swap(ref Unsafe.Add(ref elemRef, 4), ref Unsafe.Add(ref elemRef, 32));
Swap(ref Unsafe.Add(ref elemRef, 5), ref Unsafe.Add(ref elemRef, 40));
Swap(ref Unsafe.Add(ref elemRef, 6), ref Unsafe.Add(ref elemRef, 48));
Swap(ref Unsafe.Add(ref elemRef, 7), ref Unsafe.Add(ref elemRef, 56));
// row #1
Swap(ref Unsafe.Add(ref elemRef, 10), ref Unsafe.Add(ref elemRef, 17));
Swap(ref Unsafe.Add(ref elemRef, 11), ref Unsafe.Add(ref elemRef, 25));
Swap(ref Unsafe.Add(ref elemRef, 12), ref Unsafe.Add(ref elemRef, 33));
Swap(ref Unsafe.Add(ref elemRef, 13), ref Unsafe.Add(ref elemRef, 41));
Swap(ref Unsafe.Add(ref elemRef, 14), ref Unsafe.Add(ref elemRef, 49));
Swap(ref Unsafe.Add(ref elemRef, 15), ref Unsafe.Add(ref elemRef, 57));
// row #2
Swap(ref Unsafe.Add(ref elemRef, 19), ref Unsafe.Add(ref elemRef, 26));
Swap(ref Unsafe.Add(ref elemRef, 20), ref Unsafe.Add(ref elemRef, 34));
Swap(ref Unsafe.Add(ref elemRef, 21), ref Unsafe.Add(ref elemRef, 42));
Swap(ref Unsafe.Add(ref elemRef, 22), ref Unsafe.Add(ref elemRef, 50));
Swap(ref Unsafe.Add(ref elemRef, 23), ref Unsafe.Add(ref elemRef, 58));
// row #3
Swap(ref Unsafe.Add(ref elemRef, 28), ref Unsafe.Add(ref elemRef, 35));
Swap(ref Unsafe.Add(ref elemRef, 29), ref Unsafe.Add(ref elemRef, 43));
Swap(ref Unsafe.Add(ref elemRef, 30), ref Unsafe.Add(ref elemRef, 51));
Swap(ref Unsafe.Add(ref elemRef, 31), ref Unsafe.Add(ref elemRef, 59));
// row #4
Swap(ref Unsafe.Add(ref elemRef, 37), ref Unsafe.Add(ref elemRef, 44));
Swap(ref Unsafe.Add(ref elemRef, 38), ref Unsafe.Add(ref elemRef, 52));
Swap(ref Unsafe.Add(ref elemRef, 39), ref Unsafe.Add(ref elemRef, 60));
// row #5
Swap(ref Unsafe.Add(ref elemRef, 46), ref Unsafe.Add(ref elemRef, 53));
Swap(ref Unsafe.Add(ref elemRef, 47), ref Unsafe.Add(ref elemRef, 61));
// row #6
Swap(ref Unsafe.Add(ref elemRef, 55), ref Unsafe.Add(ref elemRef, 62));
static void Swap(ref float a, ref float b)
{
float tmp = a;
a = b;
b = tmp;
}
}
[MethodImpl(InliningOptions.ShortMethod)]
private static Vector<float> NormalizeAndRound(Vector<float> row, Vector<float> off, Vector<float> max)
{
row += off;
row = Vector.Max(row, Vector<float>.Zero);
row = Vector.Min(row, max);
return row.FastRound();
}
} }
} }

47
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx2.cs

@ -22,60 +22,39 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result) protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{ {
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> cBase = ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> mBase = ref Vector256<float> c1Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> yBase = ref Vector256<float> c2Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> kBase = ref Vector256<float> c3Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
ref Vector256<float> resultBase =
ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(result));
// Used for the color conversion // Used for the color conversion
var scale = Vector256.Create(1 / this.MaximumValue); var scale = Vector256.Create(1 / this.MaximumValue);
var one = Vector256.Create(1F);
// Used for packing
ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32);
Vector256<int> vcontrol = Unsafe.As<byte, Vector256<int>>(ref control);
int n = result.Length / 8; nint n = values.Component0.Length / 8;
for (int i = 0; i < n; i++) for (nint i = 0; i < n; i++)
{ {
Vector256<float> k = Avx2.PermuteVar8x32(Unsafe.Add(ref kBase, i), vcontrol); ref Vector256<float> c = ref Unsafe.Add(ref c0Base, i);
Vector256<float> c = Avx2.PermuteVar8x32(Unsafe.Add(ref cBase, i), vcontrol); ref Vector256<float> m = ref Unsafe.Add(ref c1Base, i);
Vector256<float> m = Avx2.PermuteVar8x32(Unsafe.Add(ref mBase, i), vcontrol); ref Vector256<float> y = ref Unsafe.Add(ref c2Base, i);
Vector256<float> y = Avx2.PermuteVar8x32(Unsafe.Add(ref yBase, i), vcontrol); Vector256<float> k = Unsafe.Add(ref c3Base, i);
k = Avx.Multiply(k, scale); k = Avx.Multiply(k, scale);
c = Avx.Multiply(Avx.Multiply(c, k), scale); c = Avx.Multiply(Avx.Multiply(c, k), scale);
m = Avx.Multiply(Avx.Multiply(m, k), scale); m = Avx.Multiply(Avx.Multiply(m, k), scale);
y = Avx.Multiply(Avx.Multiply(y, k), scale); y = Avx.Multiply(Avx.Multiply(y, k), scale);
Vector256<float> cmLo = Avx.UnpackLow(c, m);
Vector256<float> yoLo = Avx.UnpackLow(y, one);
Vector256<float> cmHi = Avx.UnpackHigh(c, m);
Vector256<float> yoHi = Avx.UnpackHigh(y, one);
ref Vector256<float> destination = ref Unsafe.Add(ref resultBase, i * 4);
destination = Avx.Shuffle(cmLo, yoLo, 0b01_00_01_00);
Unsafe.Add(ref destination, 1) = Avx.Shuffle(cmLo, yoLo, 0b11_10_11_10);
Unsafe.Add(ref destination, 2) = Avx.Shuffle(cmHi, yoHi, 0b01_00_01_00);
Unsafe.Add(ref destination, 3) = Avx.Shuffle(cmHi, yoHi, 0b11_10_11_10);
} }
#endif #endif
} }
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) => protected override void ConvertCoreInplace(in ComponentValues values) =>
FromCmykBasic.ConvertCore(values, result, this.MaximumValue); FromCmykBasic.ConvertCoreInplace(values, this.MaximumValue);
} }
} }
} }

45
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykBasic.cs

@ -15,38 +15,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result) public override void ConvertToRgbInplace(in ComponentValues values) =>
{ ConvertCoreInplace(values, this.MaximumValue);
ConvertCore(values, result, this.MaximumValue);
}
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue) internal static void ConvertCoreInplace(in ComponentValues values, float maxValue)
{ {
ReadOnlySpan<float> cVals = values.Component0; Span<float> c0 = values.Component0;
ReadOnlySpan<float> mVals = values.Component1; Span<float> c1 = values.Component1;
ReadOnlySpan<float> yVals = values.Component2; Span<float> c2 = values.Component2;
ReadOnlySpan<float> kVals = values.Component3; Span<float> c3 = values.Component3;
var v = new Vector4(0, 0, 0, 1F);
var maximum = 1 / maxValue;
var scale = new Vector4(maximum, maximum, maximum, 1F);
for (int i = 0; i < result.Length; i++) float scale = 1 / maxValue;
for (int i = 0; i < c0.Length; i++)
{ {
float c = cVals[i]; float c = c0[i];
float m = mVals[i]; float m = c1[i];
float y = yVals[i]; float y = c2[i];
float k = kVals[i] / maxValue; float k = c3[i] / maxValue;
v.X = c * k; c0[i] = c * k * scale;
v.Y = m * k; c1[i] = m * k * scale;
v.Z = y * k; c2[i] = y * k * scale;
v.W = 1F;
v *= scale;
result[i] = v;
} }
} }
} }

34
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result) protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{ {
ref Vector<float> cBase = ref Vector<float> cBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
@ -29,43 +29,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
ref Vector<float> kBase = ref Vector<float> kBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3));
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));
Vector4Pair cc = default;
Vector4Pair mm = default;
Vector4Pair yy = default;
ref Vector<float> ccRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref cc);
ref Vector<float> mmRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref mm);
ref Vector<float> yyRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref yy);
var scale = new Vector<float>(1 / this.MaximumValue); var scale = new Vector<float>(1 / this.MaximumValue);
// Walking 8 elements at one step: // Walking 8 elements at one step:
int n = result.Length / 8; nint n = values.Component0.Length / 8;
for (int i = 0; i < n; i++) for (nint i = 0; i < n; i++)
{ {
Vector<float> c = Unsafe.Add(ref cBase, i); ref Vector<float> c = ref Unsafe.Add(ref cBase, i);
Vector<float> m = Unsafe.Add(ref mBase, i); ref Vector<float> m = ref Unsafe.Add(ref mBase, i);
Vector<float> y = Unsafe.Add(ref yBase, i); ref Vector<float> y = ref Unsafe.Add(ref yBase, i);
Vector<float> k = Unsafe.Add(ref kBase, i) * scale; Vector<float> k = Unsafe.Add(ref kBase, i) * scale;
c = (c * k) * scale; c = (c * k) * scale;
m = (m * k) * scale; m = (m * k) * scale;
y = (y * k) * scale; y = (y * k) * scale;
ccRefAsVector = c;
mmRefAsVector = m;
yyRefAsVector = y;
// Collect (c0,c1...c8) (m0,m1...m8) (y0,y1...y8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order:
ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i);
destination.Pack(ref cc, ref mm, ref yy);
} }
} }
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) => protected override void ConvertCoreInplace(in ComponentValues values) =>
FromCmykBasic.ConvertCore(values, result, this.MaximumValue); FromCmykBasic.ConvertCoreInplace(values, this.MaximumValue);
} }
} }
} }

32
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx2.cs

@ -22,42 +22,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result) protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{ {
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> gBase = ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> resultBase =
ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(result));
// Used for the color conversion // Used for the color conversion
var scale = Vector256.Create(1 / this.MaximumValue); var scale = Vector256.Create(1 / this.MaximumValue);
var one = Vector256.Create(1F);
// Used for packing
ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32);
Vector256<int> vcontrol = Unsafe.As<byte, Vector256<int>>(ref control);
int n = result.Length / 8; nint n = values.Component0.Length / 8;
for (int i = 0; i < n; i++) for (nint i = 0; i < n; i++)
{ {
Vector256<float> g = Avx.Multiply(Unsafe.Add(ref gBase, i), scale); ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i);
c0 = Avx.Multiply(c0, scale);
g = Avx2.PermuteVar8x32(g, vcontrol);
ref Vector256<float> destination = ref Unsafe.Add(ref resultBase, i * 4);
destination = Avx.Blend(Avx.Permute(g, 0b00_00_00_00), one, 0b1000_1000);
Unsafe.Add(ref destination, 1) = Avx.Blend(Avx.Shuffle(g, g, 0b01_01_01_01), one, 0b1000_1000);
Unsafe.Add(ref destination, 2) = Avx.Blend(Avx.Shuffle(g, g, 0b10_10_10_10), one, 0b1000_1000);
Unsafe.Add(ref destination, 3) = Avx.Blend(Avx.Shuffle(g, g, 0b11_11_11_11), one, 0b1000_1000);
} }
#endif #endif
} }
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) => protected override void ConvertCoreInplace(in ComponentValues values) =>
FromGrayscaleBasic.ConvertCore(values, result, this.MaximumValue); FromGrayscaleBasic.ScaleValues(values.Component0, this.MaximumValue);
} }
} }
} }

38
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleBasic.cs

@ -17,25 +17,35 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result) public override void ConvertToRgbInplace(in ComponentValues values) =>
{ ScaleValues(values.Component0, this.MaximumValue);
ConvertCore(values, result, this.MaximumValue);
}
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue) internal static void ScaleValues(Span<float> values, float maxValue)
{ {
var maximum = 1 / maxValue; Span<Vector4> vecValues = MemoryMarshal.Cast<float, Vector4>(values);
var scale = new Vector4(maximum, maximum, maximum, 1F);
ref float sBase = ref MemoryMarshal.GetReference(values.Component0); var scaleVector = new Vector4(1 / maxValue);
ref Vector4 dBase = ref MemoryMarshal.GetReference(result);
for (int i = 0; i < result.Length; i++) for (int i = 0; i < vecValues.Length; i++)
{ {
var v = new Vector4(Unsafe.Add(ref sBase, i)); vecValues[i] *= scaleVector;
v.W = 1f; }
v *= scale;
Unsafe.Add(ref dBase, i) = v; values = values.Slice(vecValues.Length * 4);
if (!values.IsEmpty)
{
float scaleValue = 1f / maxValue;
values[0] *= scaleValue;
if ((uint)values.Length > 1)
{
values[1] *= scaleValue;
if ((uint)values.Length > 2)
{
values[2] *= scaleValue;
}
}
} }
} }
} }

40
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs

@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result) protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{ {
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> rBase = ref Vector256<float> rBase =
@ -32,41 +32,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
ref Vector256<float> bBase = ref Vector256<float> bBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> resultBase =
ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(result));
// Used for the color conversion // Used for the color conversion
var scale = Vector256.Create(1 / this.MaximumValue); var scale = Vector256.Create(1 / this.MaximumValue);
var one = Vector256.Create(1F); nint n = values.Component0.Length / 8;
for (nint i = 0; i < n; i++)
// Used for packing
ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32);
Vector256<int> vcontrol = Unsafe.As<byte, Vector256<int>>(ref control);
int n = result.Length / 8;
for (int i = 0; i < n; i++)
{ {
Vector256<float> r = Avx.Multiply(Avx2.PermuteVar8x32(Unsafe.Add(ref rBase, i), vcontrol), scale); ref Vector256<float> r = ref Unsafe.Add(ref rBase, i);
Vector256<float> g = Avx.Multiply(Avx2.PermuteVar8x32(Unsafe.Add(ref gBase, i), vcontrol), scale); ref Vector256<float> g = ref Unsafe.Add(ref gBase, i);
Vector256<float> b = Avx.Multiply(Avx2.PermuteVar8x32(Unsafe.Add(ref bBase, i), vcontrol), scale); ref Vector256<float> b = ref Unsafe.Add(ref bBase, i);
r = Avx.Multiply(r, scale);
Vector256<float> rgLo = Avx.UnpackLow(r, g); g = Avx.Multiply(g, scale);
Vector256<float> boLo = Avx.UnpackLow(b, one); b = Avx.Multiply(b, scale);
Vector256<float> rgHi = Avx.UnpackHigh(r, g);
Vector256<float> boHi = Avx.UnpackHigh(b, one);
ref Vector256<float> destination = ref Unsafe.Add(ref resultBase, i * 4);
destination = Avx.Shuffle(rgLo, boLo, 0b01_00_01_00);
Unsafe.Add(ref destination, 1) = Avx.Shuffle(rgLo, boLo, 0b11_10_11_10);
Unsafe.Add(ref destination, 2) = Avx.Shuffle(rgHi, boHi, 0b01_00_01_00);
Unsafe.Add(ref destination, 3) = Avx.Shuffle(rgHi, boHi, 0b11_10_11_10);
} }
#endif #endif
} }
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) => protected override void ConvertCoreInplace(in ComponentValues values) =>
FromRgbBasic.ConvertCore(values, result, this.MaximumValue); FromRgbBasic.ConvertCoreInplace(values, this.MaximumValue);
} }
} }
} }

33
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs

@ -3,6 +3,7 @@
using System; using System;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
@ -15,36 +16,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result) public override void ConvertToRgbInplace(in ComponentValues values)
{ {
ConvertCore(values, result, this.MaximumValue); ConvertCoreInplace(values, this.MaximumValue);
} }
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue) internal static void ConvertCoreInplace(ComponentValues values, float maxValue)
{ {
ReadOnlySpan<float> rVals = values.Component0; FromGrayscaleBasic.ScaleValues(values.Component0, maxValue);
ReadOnlySpan<float> gVals = values.Component1; FromGrayscaleBasic.ScaleValues(values.Component1, maxValue);
ReadOnlySpan<float> bVals = values.Component2; FromGrayscaleBasic.ScaleValues(values.Component2, maxValue);
var v = new Vector4(0, 0, 0, 1);
var maximum = 1 / maxValue;
var scale = new Vector4(maximum, maximum, maximum, 1F);
for (int i = 0; i < result.Length; i++)
{
float r = rVals[i];
float g = gVals[i];
float b = bVals[i];
v.X = r;
v.Y = g;
v.Z = b;
v *= scale;
result[i] = v;
}
} }
} }
} }

40
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs

@ -18,50 +18,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result) protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{ {
ref Vector<float> rBase = ref Vector<float> rBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> gBase = ref Vector<float> gBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> bBase = ref Vector<float> bBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));
Vector4Pair rr = default;
Vector4Pair gg = default;
Vector4Pair bb = default;
ref Vector<float> rrRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref rr);
ref Vector<float> ggRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref gg);
ref Vector<float> bbRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref bb);
var scale = new Vector<float>(1 / this.MaximumValue); var scale = new Vector<float>(1 / this.MaximumValue);
// Walking 8 elements at one step: // Walking 8 elements at one step:
int n = result.Length / 8; nint n = values.Component0.Length / 8;
for (int i = 0; i < n; i++) for (nint i = 0; i < n; i++)
{ {
Vector<float> r = Unsafe.Add(ref rBase, i); ref Vector<float> r = ref Unsafe.Add(ref rBase, i);
Vector<float> g = Unsafe.Add(ref gBase, i); ref Vector<float> g = ref Unsafe.Add(ref gBase, i);
Vector<float> b = Unsafe.Add(ref bBase, i); ref Vector<float> b = ref Unsafe.Add(ref bBase, i);
r *= scale; r *= scale;
g *= scale; g *= scale;
b *= scale; b *= scale;
rrRefAsVector = r;
ggRefAsVector = g;
bbRefAsVector = b;
// Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order:
ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i);
destination.Pack(ref rr, ref gg, ref bb);
} }
} }
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) => protected override void ConvertCoreInplace(in ComponentValues values) =>
FromRgbBasic.ConvertCore(values, result, this.MaximumValue); FromRgbBasic.ConvertCoreInplace(values, this.MaximumValue);
} }
} }
} }

50
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx2.cs

@ -23,19 +23,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result) protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{ {
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> yBase = ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> cbBase = ref Vector256<float> c1Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> crBase = ref Vector256<float> c2Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> resultBase =
ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(result));
// Used for the color conversion // Used for the color conversion
var chromaOffset = Vector256.Create(-this.HalfValue); var chromaOffset = Vector256.Create(-this.HalfValue);
var scale = Vector256.Create(1 / this.MaximumValue); var scale = Vector256.Create(1 / this.MaximumValue);
@ -50,19 +47,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
Vector256<int> vcontrol = Unsafe.As<byte, Vector256<int>>(ref control); Vector256<int> vcontrol = Unsafe.As<byte, Vector256<int>>(ref control);
// Walking 8 elements at one step: // Walking 8 elements at one step:
int n = result.Length / 8; nint n = values.Component0.Length / 8;
for (int i = 0; i < n; i++) for (nint i = 0; i < n; i++)
{ {
// y = yVals[i]; // y = yVals[i];
// cb = cbVals[i] - 128F; // cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F; // cr = crVals[i] - 128F;
Vector256<float> y = Unsafe.Add(ref yBase, i); ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i);
Vector256<float> cb = Avx.Add(Unsafe.Add(ref cbBase, i), chromaOffset); ref Vector256<float> c1 = ref Unsafe.Add(ref c1Base, i);
Vector256<float> cr = Avx.Add(Unsafe.Add(ref crBase, i), chromaOffset); ref Vector256<float> c2 = ref Unsafe.Add(ref c2Base, i);
y = Avx2.PermuteVar8x32(y, vcontrol); Vector256<float> y = c0;
cb = Avx2.PermuteVar8x32(cb, vcontrol); Vector256<float> cb = Avx.Add(c1, chromaOffset);
cr = Avx2.PermuteVar8x32(cr, vcontrol); Vector256<float> cr = Avx.Add(c2, chromaOffset);
// r = y + (1.402F * cr); // r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr);
@ -72,30 +69,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
Vector256<float> g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); Vector256<float> g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
Vector256<float> b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult); Vector256<float> b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult);
// TODO: We should be saving to RGBA not Vector4
r = Avx.Multiply(Avx.RoundToNearestInteger(r), scale); r = Avx.Multiply(Avx.RoundToNearestInteger(r), scale);
g = Avx.Multiply(Avx.RoundToNearestInteger(g), scale); g = Avx.Multiply(Avx.RoundToNearestInteger(g), scale);
b = Avx.Multiply(Avx.RoundToNearestInteger(b), scale); b = Avx.Multiply(Avx.RoundToNearestInteger(b), scale);
Vector256<float> vte = Avx.UnpackLow(r, b); c0 = r;
Vector256<float> vto = Avx.UnpackLow(g, va); c1 = g;
c2 = b;
ref Vector256<float> destination = ref Unsafe.Add(ref resultBase, i * 4);
destination = Avx.UnpackLow(vte, vto);
Unsafe.Add(ref destination, 1) = Avx.UnpackHigh(vte, vto);
vte = Avx.UnpackHigh(r, b);
vto = Avx.UnpackHigh(g, va);
Unsafe.Add(ref destination, 2) = Avx.UnpackLow(vte, vto);
Unsafe.Add(ref destination, 3) = Avx.UnpackHigh(vte, vto);
} }
#endif #endif
} }
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) => protected override void ConvertCoreInplace(in ComponentValues values) =>
FromYCbCrBasic.ConvertCore(values, result, this.MaximumValue, this.HalfValue); FromYCbCrBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
} }
} }
} }

37
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs

@ -15,35 +15,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result) public override void ConvertToRgbInplace(in ComponentValues values)
{ => ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
ConvertCore(values, result, this.MaximumValue, this.HalfValue);
}
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue, float halfValue) internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue)
{ {
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()! Span<float> c0 = values.Component0;
ReadOnlySpan<float> yVals = values.Component0; Span<float> c1 = values.Component1;
ReadOnlySpan<float> cbVals = values.Component1; Span<float> c2 = values.Component2;
ReadOnlySpan<float> crVals = values.Component2;
var v = new Vector4(0, 0, 0, 1);
var scale = new Vector4(1 / maxValue, 1 / maxValue, 1 / maxValue, 1F); var scale = 1 / maxValue;
for (int i = 0; i < result.Length; i++) for (int i = 0; i < c0.Length; i++)
{ {
float y = yVals[i]; float y = c0[i];
float cb = cbVals[i] - halfValue; float cb = c1[i] - halfValue;
float cr = crVals[i] - halfValue; float cr = c2[i] - halfValue;
v.X = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero);
v.Y = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero);
v.Z = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero);
v *= scale;
result[i] = v; c0[i] = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero) * scale;
c1[i] = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero) * scale;
c2[i] = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero) * scale;
} }
} }
} }

52
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector4.cs

@ -20,58 +20,54 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
protected override bool IsAvailable => SimdUtils.HasVector4; protected override bool IsAvailable => SimdUtils.HasVector4;
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result) protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{ {
// TODO: Find a way to properly run & test this path on AVX2 PC-s! (Have I already mentioned that Vector<T> is terrible?) DebugGuard.IsTrue(values.Component0.Length % 8 == 0, nameof(values), "Length should be divisible by 8!");
DebugGuard.IsTrue(result.Length % 8 == 0, nameof(result), "result.Length should be divisible by 8!");
ref Vector4Pair yBase = ref Vector4Pair c0Base =
ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component0)); ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector4Pair cbBase = ref Vector4Pair c1Base =
ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component1)); ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector4Pair crBase = ref Vector4Pair c2Base =
ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component2)); ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));
var chromaOffset = new Vector4(-this.HalfValue); var chromaOffset = new Vector4(-this.HalfValue);
var maxValue = this.MaximumValue; var maxValue = this.MaximumValue;
// Walking 8 elements at one step: // Walking 8 elements at one step:
int n = result.Length / 8; nint n = values.Component0.Length / 8;
for (int i = 0; i < n; i++) for (nint i = 0; i < n; i++)
{ {
// y = yVals[i]; // y = yVals[i];
Vector4Pair y = Unsafe.Add(ref yBase, i); ref Vector4Pair c0 = ref Unsafe.Add(ref c0Base, i);
// cb = cbVals[i] - halfValue); // cb = cbVals[i] - halfValue);
Vector4Pair cb = Unsafe.Add(ref cbBase, i); ref Vector4Pair c1 = ref Unsafe.Add(ref c1Base, i);
cb.AddInplace(chromaOffset); c1.AddInplace(chromaOffset);
// cr = crVals[i] - halfValue; // cr = crVals[i] - halfValue;
Vector4Pair cr = Unsafe.Add(ref crBase, i); ref Vector4Pair c2 = ref Unsafe.Add(ref c2Base, i);
cr.AddInplace(chromaOffset); c2.AddInplace(chromaOffset);
// r = y + (1.402F * cr); // r = y + (1.402F * cr);
Vector4Pair r = y; Vector4Pair r = c0;
Vector4Pair tmp = cr; Vector4Pair tmp = c2;
tmp.MultiplyInplace(1.402F); tmp.MultiplyInplace(1.402F);
r.AddInplace(ref tmp); r.AddInplace(ref tmp);
// g = y - (0.344136F * cb) - (0.714136F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr);
Vector4Pair g = y; Vector4Pair g = c0;
tmp = cb; tmp = c1;
tmp.MultiplyInplace(-0.344136F); tmp.MultiplyInplace(-0.344136F);
g.AddInplace(ref tmp); g.AddInplace(ref tmp);
tmp = cr; tmp = c2;
tmp.MultiplyInplace(-0.714136F); tmp.MultiplyInplace(-0.714136F);
g.AddInplace(ref tmp); g.AddInplace(ref tmp);
// b = y + (1.772F * cb); // b = y + (1.772F * cb);
Vector4Pair b = y; Vector4Pair b = c0;
tmp = cb; tmp = c1;
tmp.MultiplyInplace(1.772F); tmp.MultiplyInplace(1.772F);
b.AddInplace(ref tmp); b.AddInplace(ref tmp);
@ -79,14 +75,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
g.RoundAndDownscalePreVector8(maxValue); g.RoundAndDownscalePreVector8(maxValue);
b.RoundAndDownscalePreVector8(maxValue); b.RoundAndDownscalePreVector8(maxValue);
// Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order: c0 = r;
ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i); c1 = g;
destination.Pack(ref r, ref g, ref b); c2 = b;
} }
} }
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) => protected override void ConvertCoreInplace(in ComponentValues values)
FromYCbCrBasic.ConvertCore(values, result, this.MaximumValue, this.HalfValue); => FromYCbCrBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
} }
} }
} }

47
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector8.cs

@ -19,41 +19,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result) protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{ {
ref Vector<float> yBase = ref Vector<float> c0Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> cbBase = ref Vector<float> c1Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> crBase = ref Vector<float> c2Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));
var chromaOffset = new Vector<float>(-this.HalfValue); var chromaOffset = new Vector<float>(-this.HalfValue);
// Walking 8 elements at one step: // Walking 8 elements at one step:
int n = result.Length / 8; nint n = values.Component0.Length / 8;
Vector4Pair rr = default;
Vector4Pair gg = default;
Vector4Pair bb = default;
ref Vector<float> rrRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref rr);
ref Vector<float> ggRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref gg);
ref Vector<float> bbRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref bb);
var scale = new Vector<float>(1 / this.MaximumValue); var scale = new Vector<float>(1 / this.MaximumValue);
for (int i = 0; i < n; i++) for (nint i = 0; i < n; i++)
{ {
// y = yVals[i]; // y = yVals[i];
// cb = cbVals[i] - 128F; // cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F; // cr = crVals[i] - 128F;
Vector<float> y = Unsafe.Add(ref yBase, i); ref Vector<float> c0 = ref Unsafe.Add(ref c0Base, i);
Vector<float> cb = Unsafe.Add(ref cbBase, i) + chromaOffset; ref Vector<float> c1 = ref Unsafe.Add(ref c1Base, i);
Vector<float> cr = Unsafe.Add(ref crBase, i) + chromaOffset; ref Vector<float> c2 = ref Unsafe.Add(ref c2Base, i);
Vector<float> y = Unsafe.Add(ref c0Base, i);
Vector<float> cb = Unsafe.Add(ref c1Base, i) + chromaOffset;
Vector<float> cr = Unsafe.Add(ref c2Base, i) + chromaOffset;
// r = y + (1.402F * cr); // r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr);
@ -70,18 +61,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
g *= scale; g *= scale;
b *= scale; b *= scale;
rrRefAsVector = r; c0 = r;
ggRefAsVector = g; c1 = g;
bbRefAsVector = b; c2 = b;
// Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order:
ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i);
destination.Pack(ref rr, ref gg, ref bb);
} }
} }
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) => protected override void ConvertCoreInplace(in ComponentValues values) =>
FromYCbCrBasic.ConvertCore(values, result, this.MaximumValue, this.HalfValue); FromYCbCrBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
} }
} }
} }

64
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx2.cs

@ -22,52 +22,42 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result) protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{ {
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> yBase = ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> cbBase = ref Vector256<float> c1Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> crBase = ref Vector256<float> c2Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> kBase = ref Vector256<float> kBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
ref Vector256<float> resultBase =
ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(result));
// Used for the color conversion // Used for the color conversion
var chromaOffset = Vector256.Create(-this.HalfValue); var chromaOffset = Vector256.Create(-this.HalfValue);
var scale = Vector256.Create(1 / this.MaximumValue); var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue));
var max = Vector256.Create(this.MaximumValue); var max = Vector256.Create(this.MaximumValue);
var rCrMult = Vector256.Create(1.402F); var rCrMult = Vector256.Create(1.402F);
var gCbMult = Vector256.Create(-0.344136F); var gCbMult = Vector256.Create(-0.344136F);
var gCrMult = Vector256.Create(-0.714136F); var gCrMult = Vector256.Create(-0.714136F);
var bCbMult = Vector256.Create(1.772F); var bCbMult = Vector256.Create(1.772F);
// Used for packing.
var va = Vector256.Create(1F);
ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32);
Vector256<int> vcontrol = Unsafe.As<byte, Vector256<int>>(ref control);
// Walking 8 elements at one step: // Walking 8 elements at one step:
int n = result.Length / 8; nint n = values.Component0.Length / 8;
for (int i = 0; i < n; i++) for (nint i = 0; i < n; i++)
{ {
// y = yVals[i]; // y = yVals[i];
// cb = cbVals[i] - 128F; // cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F; // cr = crVals[i] - 128F;
// k = kVals[i] / 256F; // k = kVals[i] / 256F;
Vector256<float> y = Unsafe.Add(ref yBase, i); ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i);
Vector256<float> cb = Avx.Add(Unsafe.Add(ref cbBase, i), chromaOffset); ref Vector256<float> c1 = ref Unsafe.Add(ref c1Base, i);
Vector256<float> cr = Avx.Add(Unsafe.Add(ref crBase, i), chromaOffset); ref Vector256<float> c2 = ref Unsafe.Add(ref c2Base, i);
Vector256<float> k = Avx.Divide(Unsafe.Add(ref kBase, i), max); Vector256<float> y = c0;
Vector256<float> cb = Avx.Add(c1, chromaOffset);
y = Avx2.PermuteVar8x32(y, vcontrol); Vector256<float> cr = Avx.Add(c2, chromaOffset);
cb = Avx2.PermuteVar8x32(cb, vcontrol); Vector256<float> scaledK = Avx.Multiply(Unsafe.Add(ref kBase, i), scale);
cr = Avx2.PermuteVar8x32(cr, vcontrol);
k = Avx2.PermuteVar8x32(k, vcontrol);
// r = y + (1.402F * cr); // r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr);
@ -82,29 +72,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
g = Avx.Subtract(max, Avx.RoundToNearestInteger(g)); g = Avx.Subtract(max, Avx.RoundToNearestInteger(g));
b = Avx.Subtract(max, Avx.RoundToNearestInteger(b)); b = Avx.Subtract(max, Avx.RoundToNearestInteger(b));
r = Avx.Multiply(Avx.Multiply(r, k), scale); r = Avx.Multiply(r, scaledK);
g = Avx.Multiply(Avx.Multiply(g, k), scale); g = Avx.Multiply(g, scaledK);
b = Avx.Multiply(Avx.Multiply(b, k), scale); b = Avx.Multiply(b, scaledK);
Vector256<float> vte = Avx.UnpackLow(r, b);
Vector256<float> vto = Avx.UnpackLow(g, va);
ref Vector256<float> destination = ref Unsafe.Add(ref resultBase, i * 4);
destination = Avx.UnpackLow(vte, vto);
Unsafe.Add(ref destination, 1) = Avx.UnpackHigh(vte, vto);
vte = Avx.UnpackHigh(r, b);
vto = Avx.UnpackHigh(g, va);
Unsafe.Add(ref destination, 2) = Avx.UnpackLow(vte, vto); c0 = r;
Unsafe.Add(ref destination, 3) = Avx.UnpackHigh(vte, vto); c1 = g;
c2 = b;
} }
#endif #endif
} }
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) => protected override void ConvertCoreInplace(in ComponentValues values) =>
FromYccKBasic.ConvertCore(values, result, this.MaximumValue, this.HalfValue); FromYccKBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
} }
} }
} }

43
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKBasic.cs

@ -15,39 +15,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result) public override void ConvertToRgbInplace(in ComponentValues values) =>
{ ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
ConvertCore(values, result, this.MaximumValue, this.HalfValue);
}
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue, float halfValue) internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue)
{ {
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()! Span<float> c0 = values.Component0;
ReadOnlySpan<float> yVals = values.Component0; Span<float> c1 = values.Component1;
ReadOnlySpan<float> cbVals = values.Component1; Span<float> c2 = values.Component2;
ReadOnlySpan<float> crVals = values.Component2; Span<float> c3 = values.Component3;
ReadOnlySpan<float> kVals = values.Component3;
var v = new Vector4(0, 0, 0, 1F); var v = new Vector4(0, 0, 0, 1F);
var maximum = 1 / maxValue; var scale = 1 / (maxValue * maxValue);
var scale = new Vector4(maximum, maximum, maximum, 1F);
for (int i = 0; i < result.Length; i++) for (int i = 0; i < values.Component0.Length; i++)
{ {
float y = yVals[i]; float y = c0[i];
float cb = cbVals[i] - halfValue; float cb = c1[i] - halfValue;
float cr = crVals[i] - halfValue; float cr = c2[i] - halfValue;
float k = kVals[i] / maxValue; float scaledK = c3[i] * scale;
v.X = (maxValue - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; c0[i] = (maxValue - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * scaledK;
v.Y = (maxValue - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k; c1[i] = (maxValue - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * scaledK;
v.Z = (maxValue - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; c2[i] = (maxValue - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * scaledK;
v.W = 1F;
v *= scale;
result[i] = v;
} }
} }
} }

60
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector8.cs

@ -18,46 +18,39 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result) protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{ {
ref Vector<float> yBase = ref Vector<float> c0Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> cbBase = ref Vector<float> c1Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> crBase = ref Vector<float> c2Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector<float> kBase = ref Vector<float> kBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3));
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));
var chromaOffset = new Vector<float>(-this.HalfValue); var chromaOffset = new Vector<float>(-this.HalfValue);
// Walking 8 elements at one step: // Walking 8 elements at one step:
int n = result.Length / 8; nint n = values.Component0.Length / 8;
Vector4Pair rr = default;
Vector4Pair gg = default;
Vector4Pair bb = default;
ref Vector<float> rrRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref rr);
ref Vector<float> ggRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref gg);
ref Vector<float> bbRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref bb);
var scale = new Vector<float>(1 / this.MaximumValue);
var max = new Vector<float>(this.MaximumValue); var max = new Vector<float>(this.MaximumValue);
var scale = new Vector<float>(1f) / (max * max);
for (int i = 0; i < n; i++) for (nint i = 0; i < n; i++)
{ {
// y = yVals[i]; // y = yVals[i];
// cb = cbVals[i] - 128F; // cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F; // cr = crVals[i] - 128F;
// k = kVals[i] / 256F; // k = kVals[i] / 256F;
Vector<float> y = Unsafe.Add(ref yBase, i); ref Vector<float> c0 = ref Unsafe.Add(ref c0Base, i);
Vector<float> cb = Unsafe.Add(ref cbBase, i) + chromaOffset; ref Vector<float> c1 = ref Unsafe.Add(ref c1Base, i);
Vector<float> cr = Unsafe.Add(ref crBase, i) + chromaOffset; ref Vector<float> c2 = ref Unsafe.Add(ref c2Base, i);
Vector<float> k = Unsafe.Add(ref kBase, i) / max;
Vector<float> y = c0;
Vector<float> cb = c1 + chromaOffset;
Vector<float> cr = c2 + chromaOffset;
Vector<float> scaledK = Unsafe.Add(ref kBase, i) * scale;
// r = y + (1.402F * cr); // r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr);
@ -67,25 +60,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
Vector<float> g = y - (cb * new Vector<float>(0.344136F)) - (cr * new Vector<float>(0.714136F)); Vector<float> g = y - (cb * new Vector<float>(0.344136F)) - (cr * new Vector<float>(0.714136F));
Vector<float> b = y + (cb * new Vector<float>(1.772F)); Vector<float> b = y + (cb * new Vector<float>(1.772F));
r = (max - r.FastRound()) * k; r = (max - r.FastRound()) * scaledK;
g = (max - g.FastRound()) * k; g = (max - g.FastRound()) * scaledK;
b = (max - b.FastRound()) * k; b = (max - b.FastRound()) * scaledK;
r *= scale;
g *= scale;
b *= scale;
rrRefAsVector = r;
ggRefAsVector = g;
bbRefAsVector = b;
// Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order: c0 = r;
ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i); c1 = g;
destination.Pack(ref rr, ref gg, ref bb); c2 = b;
} }
} }
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) => protected override void ConvertCoreInplace(in ComponentValues values) =>
FromYccKBasic.ConvertCore(values, result, this.MaximumValue, this.HalfValue); FromYccKBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
} }
} }
} }

15
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs

@ -18,10 +18,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
this.vectorSize = vectorSize; this.vectorSize = vectorSize;
} }
public sealed override void ConvertToRgba(in ComponentValues values, Span<Vector4> result) public override void ConvertToRgbInplace(in ComponentValues values)
{ {
int remainder = result.Length % this.vectorSize; int length = values.Component0.Length;
int simdCount = result.Length - remainder; int remainder = values.Component0.Length % this.vectorSize;
int simdCount = length - remainder;
if (simdCount > 0) if (simdCount > 0)
{ {
// This implementation is actually AVX specific. // This implementation is actually AVX specific.
@ -32,15 +33,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
"This converter can be used only on architecture having 256 byte floating point SIMD registers!"); "This converter can be used only on architecture having 256 byte floating point SIMD registers!");
} }
this.ConvertCoreVectorized(values.Slice(0, simdCount), result.Slice(0, simdCount)); this.ConvertCoreVectorizedInplace(values.Slice(0, simdCount));
} }
this.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder)); this.ConvertCoreInplace(values.Slice(simdCount, remainder));
} }
protected abstract void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result); protected virtual void ConvertCoreVectorizedInplace(in ComponentValues values) => throw new NotImplementedException();
protected abstract void ConvertCore(in ComponentValues values, Span<Vector4> result); protected virtual void ConvertCoreInplace(in ComponentValues values) => throw new NotImplementedException();
} }
} }
} }

156
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs

@ -76,11 +76,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
} }
/// <summary> /// <summary>
/// He implementation of the conversion. /// Converts planar jpeg component values in <paramref name="values"/> to RGB color space inplace.
/// </summary> /// </summary>
/// <param name="values">The input as a stack-only <see cref="ComponentValues"/> struct</param> /// <param name="values">The input/ouptut as a stack-only <see cref="ComponentValues"/> struct</param>
/// <param name="result">The destination buffer of <see cref="Vector4"/> values</param> public abstract void ConvertToRgbInplace(in ComponentValues values);
public abstract void ConvertToRgba(in ComponentValues values, Span<Vector4> result);
/// <summary> /// <summary>
/// Returns the <see cref="JpegColorConverter"/>s for all supported colorspaces and precisions. /// Returns the <see cref="JpegColorConverter"/>s for all supported colorspaces and precisions.
@ -181,22 +180,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
/// <summary> /// <summary>
/// The component 0 (eg. Y) /// The component 0 (eg. Y)
/// </summary> /// </summary>
public readonly ReadOnlySpan<float> Component0; public readonly Span<float> Component0;
/// <summary> /// <summary>
/// The component 1 (eg. Cb) /// The component 1 (eg. Cb). In case of grayscale, it points to <see cref="Component0"/>.
/// </summary> /// </summary>
public readonly ReadOnlySpan<float> Component1; public readonly Span<float> Component1;
/// <summary> /// <summary>
/// The component 2 (eg. Cr) /// The component 2 (eg. Cr). In case of grayscale, it points to <see cref="Component0"/>.
/// </summary> /// </summary>
public readonly ReadOnlySpan<float> Component2; public readonly Span<float> Component2;
/// <summary> /// <summary>
/// The component 4 /// The component 4
/// </summary> /// </summary>
public readonly ReadOnlySpan<float> Component3; public readonly Span<float> Component3;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ComponentValues"/> struct. /// Initializes a new instance of the <see cref="ComponentValues"/> struct.
@ -208,30 +207,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
this.ComponentCount = componentBuffers.Count; this.ComponentCount = componentBuffers.Count;
this.Component0 = componentBuffers[0].GetRowSpan(row); this.Component0 = componentBuffers[0].GetRowSpan(row);
this.Component1 = Span<float>.Empty;
this.Component2 = Span<float>.Empty; // In case of grayscale, Component1 and Component2 point to Component0 memory area
this.Component3 = Span<float>.Empty; this.Component1 = this.ComponentCount > 1 ? componentBuffers[1].GetRowSpan(row) : this.Component0;
this.Component2 = this.ComponentCount > 2 ? componentBuffers[2].GetRowSpan(row) : this.Component0;
if (this.ComponentCount > 1) this.Component3 = this.ComponentCount > 3 ? componentBuffers[3].GetRowSpan(row) : Span<float>.Empty;
{
this.Component1 = componentBuffers[1].GetRowSpan(row);
if (this.ComponentCount > 2)
{
this.Component2 = componentBuffers[2].GetRowSpan(row);
if (this.ComponentCount > 3)
{
this.Component3 = componentBuffers[3].GetRowSpan(row);
}
}
}
} }
private ComponentValues( internal ComponentValues(
int componentCount, int componentCount,
ReadOnlySpan<float> c0, Span<float> c0,
ReadOnlySpan<float> c1, Span<float> c1,
ReadOnlySpan<float> c2, Span<float> c2,
ReadOnlySpan<float> c3) Span<float> c3)
{ {
this.ComponentCount = componentCount; this.ComponentCount = componentCount;
this.Component0 = c0; this.Component0 = c0;
@ -242,111 +230,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
public ComponentValues Slice(int start, int length) public ComponentValues Slice(int start, int length)
{ {
ReadOnlySpan<float> c0 = this.Component0.Slice(start, length); Span<float> c0 = this.Component0.Slice(start, length);
ReadOnlySpan<float> c1 = this.ComponentCount > 1 ? this.Component1.Slice(start, length) : ReadOnlySpan<float>.Empty; Span<float> c1 = this.Component1.Length > 0 ? this.Component1.Slice(start, length) : Span<float>.Empty;
ReadOnlySpan<float> c2 = this.ComponentCount > 2 ? this.Component2.Slice(start, length) : ReadOnlySpan<float>.Empty; Span<float> c2 = this.Component2.Length > 0 ? this.Component2.Slice(start, length) : Span<float>.Empty;
ReadOnlySpan<float> c3 = this.ComponentCount > 3 ? this.Component3.Slice(start, length) : ReadOnlySpan<float>.Empty; Span<float> c3 = this.Component3.Length > 0 ? this.Component3.Slice(start, length) : Span<float>.Empty;
return new ComponentValues(this.ComponentCount, c0, c1, c2, c3); return new ComponentValues(this.ComponentCount, c0, c1, c2, c3);
} }
} }
internal struct Vector4Octet
{
#pragma warning disable SA1132 // Do not combine fields
public Vector4 V0, V1, V2, V3, V4, V5, V6, V7;
/// <summary>
/// Pack (r0,r1...r7) (g0,g1...g7) (b0,b1...b7) vector values as (r0,g0,b0,1), (r1,g1,b1,1) ...
/// </summary>
public void Pack(ref Vector4Pair r, ref Vector4Pair g, ref Vector4Pair b)
{
this.V0.X = r.A.X;
this.V0.Y = g.A.X;
this.V0.Z = b.A.X;
this.V0.W = 1f;
this.V1.X = r.A.Y;
this.V1.Y = g.A.Y;
this.V1.Z = b.A.Y;
this.V1.W = 1f;
this.V2.X = r.A.Z;
this.V2.Y = g.A.Z;
this.V2.Z = b.A.Z;
this.V2.W = 1f;
this.V3.X = r.A.W;
this.V3.Y = g.A.W;
this.V3.Z = b.A.W;
this.V3.W = 1f;
this.V4.X = r.B.X;
this.V4.Y = g.B.X;
this.V4.Z = b.B.X;
this.V4.W = 1f;
this.V5.X = r.B.Y;
this.V5.Y = g.B.Y;
this.V5.Z = b.B.Y;
this.V5.W = 1f;
this.V6.X = r.B.Z;
this.V6.Y = g.B.Z;
this.V6.Z = b.B.Z;
this.V6.W = 1f;
this.V7.X = r.B.W;
this.V7.Y = g.B.W;
this.V7.Z = b.B.W;
this.V7.W = 1f;
}
/// <summary>
/// Pack (g0,g1...g7) vector values as (g0,g0,g0,1), (g1,g1,g1,1) ...
/// </summary>
public void Pack(ref Vector4Pair g)
{
this.V0.X = g.A.X;
this.V0.Y = g.A.X;
this.V0.Z = g.A.X;
this.V0.W = 1f;
this.V1.X = g.A.Y;
this.V1.Y = g.A.Y;
this.V1.Z = g.A.Y;
this.V1.W = 1f;
this.V2.X = g.A.Z;
this.V2.Y = g.A.Z;
this.V2.Z = g.A.Z;
this.V2.W = 1f;
this.V3.X = g.A.W;
this.V3.Y = g.A.W;
this.V3.Z = g.A.W;
this.V3.W = 1f;
this.V4.X = g.B.X;
this.V4.Y = g.B.X;
this.V4.Z = g.B.X;
this.V4.W = 1f;
this.V5.X = g.B.Y;
this.V5.Y = g.B.Y;
this.V5.Z = g.B.Y;
this.V5.W = 1f;
this.V6.X = g.B.Z;
this.V6.Y = g.B.Z;
this.V6.Z = g.B.Z;
this.V6.W = 1f;
this.V7.X = g.B.W;
this.V7.Y = g.B.W;
this.V7.Z = g.B.W;
this.V7.W = 1f;
}
}
} }
} }

6
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs

@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public bool HasBadMarker() => this.Marker != JpegConstants.Markers.XFF && !this.HasRestartMarker(); public bool HasBadMarker() => this.Marker != JpegConstants.Markers.XFF && !this.HasRestartMarker();
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.AlwaysInline)]
public void FillBuffer() public void FillBuffer()
{ {
// Attempt to load at least the minimum number of required bits into the buffer. // Attempt to load at least the minimum number of required bits into the buffer.
@ -130,7 +130,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public int PeekBits(int nbits) => (int)ExtractBits(this.data, this.remainingBits - nbits, nbits); public int PeekBits(int nbits) => (int)ExtractBits(this.data, this.remainingBits - nbits, nbits);
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.AlwaysInline)]
private static ulong ExtractBits(ulong value, int offset, int size) => (value >> offset) & (ulong)((1 << size) - 1); private static ulong ExtractBits(ulong value, int offset, int size) => (value >> offset) & (ulong)((1 << size) - 1);
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
@ -207,7 +207,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
} }
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.AlwaysInline)]
private int ReadStream() private int ReadStream()
{ {
int value = this.badData ? 0 : this.stream.ReadByte(); int value = this.badData ? 0 : this.stream.ReadByte();

231
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs

@ -16,94 +16,113 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
internal class HuffmanScanDecoder internal class HuffmanScanDecoder
{ {
private readonly JpegFrame frame;
private readonly HuffmanTable[] dcHuffmanTables;
private readonly HuffmanTable[] acHuffmanTables;
private readonly BufferedReadStream stream; private readonly BufferedReadStream stream;
private readonly JpegComponent[] components;
// The restart interval.
private readonly int restartInterval;
// The number of interleaved components. /// <summary>
private readonly int componentsLength; /// <see cref="JpegFrame"/> instance containing decoding-related information.
/// </summary>
// The spectral selection start. private JpegFrame frame;
private readonly int spectralStart;
// The spectral selection end. /// <summary>
private readonly int spectralEnd; /// Shortcut for <see cref="frame"/>.Components.
/// </summary>
private JpegComponent[] components;
// The successive approximation high bit end. /// <summary>
private readonly int successiveHigh; /// Number of component in the current scan.
/// </summary>
private int componentsCount;
// The successive approximation low bit end. /// <summary>
private readonly int successiveLow; /// The reset interval determined by RST markers.
/// </summary>
private int restartInterval;
// How many mcu's are left to do. /// <summary>
/// How many mcu's are left to do.
/// </summary>
private int todo; private int todo;
// The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero. /// <summary>
/// The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero.
/// </summary>
private int eobrun; private int eobrun;
// The unzig data. /// <summary>
private ZigZag dctZigZag; /// The DC Huffman tables.
/// </summary>
private readonly HuffmanTable[] dcHuffmanTables;
/// <summary>
/// The AC Huffman tables
/// </summary>
private readonly HuffmanTable[] acHuffmanTables;
private HuffmanScanBuffer scanBuffer; private HuffmanScanBuffer scanBuffer;
private CancellationToken cancellationToken; private readonly SpectralConverter spectralConverter;
private readonly CancellationToken cancellationToken;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HuffmanScanDecoder"/> class. /// Initializes a new instance of the <see cref="HuffmanScanDecoder"/> class.
/// </summary> /// </summary>
/// <param name="stream">The input stream.</param> /// <param name="stream">The input stream.</param>
/// <param name="frame">The image frame.</param> /// <param name="converter">Spectral to pixel converter.</param>
/// <param name="dcHuffmanTables">The DC Huffman tables.</param>
/// <param name="acHuffmanTables">The AC Huffman tables.</param>
/// <param name="componentsLength">The length of the components. Different to the array length.</param>
/// <param name="restartInterval">The reset interval.</param>
/// <param name="spectralStart">The spectral selection start.</param>
/// <param name="spectralEnd">The spectral selection end.</param>
/// <param name="successiveHigh">The successive approximation bit high end.</param>
/// <param name="successiveLow">The successive approximation bit low end.</param>
/// <param name="cancellationToken">The token to monitor cancellation.</param> /// <param name="cancellationToken">The token to monitor cancellation.</param>
public HuffmanScanDecoder( public HuffmanScanDecoder(
BufferedReadStream stream, BufferedReadStream stream,
JpegFrame frame, SpectralConverter converter,
HuffmanTable[] dcHuffmanTables,
HuffmanTable[] acHuffmanTables,
int componentsLength,
int restartInterval,
int spectralStart,
int spectralEnd,
int successiveHigh,
int successiveLow,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
this.dctZigZag = ZigZag.CreateUnzigTable();
this.stream = stream; this.stream = stream;
this.scanBuffer = new HuffmanScanBuffer(stream); this.spectralConverter = converter;
this.frame = frame;
this.dcHuffmanTables = dcHuffmanTables;
this.acHuffmanTables = acHuffmanTables;
this.components = frame.Components;
this.componentsLength = componentsLength;
this.restartInterval = restartInterval;
this.todo = restartInterval;
this.spectralStart = spectralStart;
this.spectralEnd = spectralEnd;
this.successiveHigh = successiveHigh;
this.successiveLow = successiveLow;
this.cancellationToken = cancellationToken; this.cancellationToken = cancellationToken;
// TODO: this is actually a variable value depending on component count
const int maxTables = 4;
this.dcHuffmanTables = new HuffmanTable[maxTables];
this.acHuffmanTables = new HuffmanTable[maxTables];
} }
/// <summary>
/// Sets reset interval determined by RST markers.
/// </summary>
public int ResetInterval
{
set
{
this.restartInterval = value;
this.todo = value;
}
}
// The spectral selection start.
public int SpectralStart { get; set; }
// The spectral selection end.
public int SpectralEnd { get; set; }
// The successive approximation high bit end.
public int SuccessiveHigh { get; set; }
// The successive approximation low bit end.
public int SuccessiveLow { get; set; }
/// <summary> /// <summary>
/// Decodes the entropy coded data. /// Decodes the entropy coded data.
/// </summary> /// </summary>
public void ParseEntropyCodedData() public void ParseEntropyCodedData(int componentCount)
{ {
this.cancellationToken.ThrowIfCancellationRequested(); this.cancellationToken.ThrowIfCancellationRequested();
this.componentsCount = componentCount;
this.scanBuffer = new HuffmanScanBuffer(this.stream);
bool fullScan = this.frame.Progressive || this.frame.MultiScan;
this.frame.AllocateComponents(fullScan);
if (!this.frame.Progressive) if (!this.frame.Progressive)
{ {
this.ParseBaselineData(); this.ParseBaselineData();
@ -119,15 +138,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
} }
} }
public void InjectFrameData(JpegFrame frame, IRawJpegData jpegData)
{
this.frame = frame;
this.components = frame.Components;
this.spectralConverter.InjectFrameData(frame, jpegData);
}
private void ParseBaselineData() private void ParseBaselineData()
{ {
if (this.componentsLength == 1) if (this.componentsCount == this.frame.ComponentCount)
{ {
this.ParseBaselineDataNonInterleaved(); this.ParseBaselineDataInterleaved();
} }
else else
{ {
this.ParseBaselineDataInterleaved(); this.ParseBaselineDataNonInterleaved();
} }
} }
@ -140,7 +167,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
ref HuffmanScanBuffer buffer = ref this.scanBuffer; ref HuffmanScanBuffer buffer = ref this.scanBuffer;
// Pre-derive the huffman table to avoid in-loop checks. // Pre-derive the huffman table to avoid in-loop checks.
for (int i = 0; i < this.componentsLength; i++) for (int i = 0; i < this.componentsCount; i++)
{ {
int order = this.frame.ComponentOrder[i]; int order = this.frame.ComponentOrder[i];
JpegComponent component = this.components[order]; JpegComponent component = this.components[order];
@ -155,12 +182,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
this.cancellationToken.ThrowIfCancellationRequested(); this.cancellationToken.ThrowIfCancellationRequested();
// decode from binary to spectral
for (int i = 0; i < mcusPerLine; i++) for (int i = 0; i < mcusPerLine; i++)
{ {
// Scan an interleaved mcu... process components in order // Scan an interleaved mcu... process components in order
int mcuRow = mcu / mcusPerLine;
int mcuCol = mcu % mcusPerLine; int mcuCol = mcu % mcusPerLine;
for (int k = 0; k < this.componentsLength; k++) for (int k = 0; k < this.componentsCount; k++)
{ {
int order = this.frame.ComponentOrder[k]; int order = this.frame.ComponentOrder[k];
JpegComponent component = this.components[order]; JpegComponent component = this.components[order];
@ -175,14 +202,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// by the basic H and V specified for the component // by the basic H and V specified for the component
for (int y = 0; y < v; y++) for (int y = 0; y < v; y++)
{ {
int blockRow = (mcuRow * v) + y; Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(y);
Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(blockRow);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (int x = 0; x < h; x++) for (int x = 0; x < h; x++)
{ {
if (buffer.NoData) if (buffer.NoData)
{ {
// It is very likely that some spectral data was decoded before we encountered EOI marker
// so we need to decode what's left and return (or maybe throw?)
this.spectralConverter.ConvertStrideBaseline();
return; return;
} }
@ -202,6 +231,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
mcu++; mcu++;
this.HandleRestart(); this.HandleRestart();
} }
// convert from spectral to actual pixels via given converter
this.spectralConverter.ConvertStrideBaseline();
} }
} }
@ -248,9 +280,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// Logic has been adapted from libjpeg. // Logic has been adapted from libjpeg.
// See Table B.3 – Scan header parameter size and values. itu-t81.pdf // See Table B.3 – Scan header parameter size and values. itu-t81.pdf
bool invalid = false; bool invalid = false;
if (this.spectralStart == 0) if (this.SpectralStart == 0)
{ {
if (this.spectralEnd != 0) if (this.SpectralEnd != 0)
{ {
invalid = true; invalid = true;
} }
@ -258,22 +290,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
else else
{ {
// Need not check Ss/Se < 0 since they came from unsigned bytes. // Need not check Ss/Se < 0 since they came from unsigned bytes.
if (this.spectralEnd < this.spectralStart || this.spectralEnd > 63) if (this.SpectralEnd < this.SpectralStart || this.SpectralEnd > 63)
{ {
invalid = true; invalid = true;
} }
// AC scans may have only one component. // AC scans may have only one component.
if (this.componentsLength != 1) if (this.componentsCount != 1)
{ {
invalid = true; invalid = true;
} }
} }
if (this.successiveHigh != 0) if (this.SuccessiveHigh != 0)
{ {
// Successive approximation refinement scan: must have Al = Ah-1. // Successive approximation refinement scan: must have Al = Ah-1.
if (this.successiveHigh - 1 != this.successiveLow) if (this.SuccessiveHigh - 1 != this.SuccessiveLow)
{ {
invalid = true; invalid = true;
} }
@ -281,14 +313,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// TODO: How does this affect 12bit jpegs. // TODO: How does this affect 12bit jpegs.
// According to libjpeg the range covers 8bit only? // According to libjpeg the range covers 8bit only?
if (this.successiveLow > 13) if (this.SuccessiveLow > 13)
{ {
invalid = true; invalid = true;
} }
if (invalid) if (invalid)
{ {
JpegThrowHelper.ThrowBadProgressiveScan(this.spectralStart, this.spectralEnd, this.successiveHigh, this.successiveLow); JpegThrowHelper.ThrowBadProgressiveScan(this.SpectralStart, this.SpectralEnd, this.SuccessiveHigh, this.SuccessiveLow);
} }
} }
@ -296,7 +328,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
this.CheckProgressiveData(); this.CheckProgressiveData();
if (this.componentsLength == 1) if (this.componentsCount == 1)
{ {
this.ParseProgressiveDataNonInterleaved(); this.ParseProgressiveDataNonInterleaved();
} }
@ -315,7 +347,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
ref HuffmanScanBuffer buffer = ref this.scanBuffer; ref HuffmanScanBuffer buffer = ref this.scanBuffer;
// Pre-derive the huffman table to avoid in-loop checks. // Pre-derive the huffman table to avoid in-loop checks.
for (int k = 0; k < this.componentsLength; k++) for (int k = 0; k < this.componentsCount; k++)
{ {
int order = this.frame.ComponentOrder[k]; int order = this.frame.ComponentOrder[k];
JpegComponent component = this.components[order]; JpegComponent component = this.components[order];
@ -330,7 +362,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// Scan an interleaved mcu... process components in order // Scan an interleaved mcu... process components in order
int mcuRow = mcu / mcusPerLine; int mcuRow = mcu / mcusPerLine;
int mcuCol = mcu % mcusPerLine; int mcuCol = mcu % mcusPerLine;
for (int k = 0; k < this.componentsLength; k++) for (int k = 0; k < this.componentsCount; k++)
{ {
int order = this.frame.ComponentOrder[k]; int order = this.frame.ComponentOrder[k];
JpegComponent component = this.components[order]; JpegComponent component = this.components[order];
@ -380,7 +412,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
int w = component.WidthInBlocks; int w = component.WidthInBlocks;
int h = component.HeightInBlocks; int h = component.HeightInBlocks;
if (this.spectralStart == 0) if (this.SpectralStart == 0)
{ {
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
dcHuffmanTable.Configure(); dcHuffmanTable.Configure();
@ -445,7 +477,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
ref short blockDataRef = ref Unsafe.As<Block8x8, short>(ref block); ref short blockDataRef = ref Unsafe.As<Block8x8, short>(ref block);
ref HuffmanScanBuffer buffer = ref this.scanBuffer; ref HuffmanScanBuffer buffer = ref this.scanBuffer;
ref ZigZag zigzag = ref this.dctZigZag;
// DC // DC
int t = buffer.DecodeHuffman(ref dcTable); int t = buffer.DecodeHuffman(ref dcTable);
@ -470,7 +501,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
i += r; i += r;
s = buffer.Receive(s); s = buffer.Receive(s);
Unsafe.Add(ref blockDataRef, zigzag[i++]) = (short)s; Unsafe.Add(ref blockDataRef, ZigZag.ZigZagOrder[i++]) = (short)s;
} }
else else
{ {
@ -489,7 +520,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
ref short blockDataRef = ref Unsafe.As<Block8x8, short>(ref block); ref short blockDataRef = ref Unsafe.As<Block8x8, short>(ref block);
ref HuffmanScanBuffer buffer = ref this.scanBuffer; ref HuffmanScanBuffer buffer = ref this.scanBuffer;
if (this.successiveHigh == 0) if (this.SuccessiveHigh == 0)
{ {
// First scan for DC coefficient, must be first // First scan for DC coefficient, must be first
int s = buffer.DecodeHuffman(ref dcTable); int s = buffer.DecodeHuffman(ref dcTable);
@ -500,20 +531,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
s += component.DcPredictor; s += component.DcPredictor;
component.DcPredictor = s; component.DcPredictor = s;
blockDataRef = (short)(s << this.successiveLow); blockDataRef = (short)(s << this.SuccessiveLow);
} }
else else
{ {
// Refinement scan for DC coefficient // Refinement scan for DC coefficient
buffer.CheckBits(); buffer.CheckBits();
blockDataRef |= (short)(buffer.GetBits(1) << this.successiveLow); blockDataRef |= (short)(buffer.GetBits(1) << this.SuccessiveLow);
} }
} }
private void DecodeBlockProgressiveAC(ref Block8x8 block, ref HuffmanTable acTable) private void DecodeBlockProgressiveAC(ref Block8x8 block, ref HuffmanTable acTable)
{ {
ref short blockDataRef = ref Unsafe.As<Block8x8, short>(ref block); ref short blockDataRef = ref Unsafe.As<Block8x8, short>(ref block);
if (this.successiveHigh == 0) if (this.SuccessiveHigh == 0)
{ {
// MCU decoding for AC initial scan (either spectral selection, // MCU decoding for AC initial scan (either spectral selection,
// or first pass of successive approximation). // or first pass of successive approximation).
@ -524,10 +555,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
} }
ref HuffmanScanBuffer buffer = ref this.scanBuffer; ref HuffmanScanBuffer buffer = ref this.scanBuffer;
ref ZigZag zigzag = ref this.dctZigZag; int start = this.SpectralStart;
int start = this.spectralStart; int end = this.SpectralEnd;
int end = this.spectralEnd; int low = this.SuccessiveLow;
int low = this.successiveLow;
for (int i = start; i <= end; ++i) for (int i = start; i <= end; ++i)
{ {
@ -540,7 +570,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
if (s != 0) if (s != 0)
{ {
s = buffer.Receive(s); s = buffer.Receive(s);
Unsafe.Add(ref blockDataRef, zigzag[i]) = (short)(s << low); Unsafe.Add(ref blockDataRef, ZigZag.ZigZagOrder[i]) = (short)(s << low);
} }
else else
{ {
@ -570,12 +600,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
// Refinement scan for these AC coefficients // Refinement scan for these AC coefficients
ref HuffmanScanBuffer buffer = ref this.scanBuffer; ref HuffmanScanBuffer buffer = ref this.scanBuffer;
ref ZigZag zigzag = ref this.dctZigZag; int start = this.SpectralStart;
int start = this.spectralStart; int end = this.SpectralEnd;
int end = this.spectralEnd;
int p1 = 1 << this.successiveLow; int p1 = 1 << this.SuccessiveLow;
int m1 = (-1) << this.successiveLow; int m1 = (-1) << this.SuccessiveLow;
int k = start; int k = start;
@ -617,7 +646,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
do do
{ {
ref short coef = ref Unsafe.Add(ref blockDataRef, zigzag[k]); ref short coef = ref Unsafe.Add(ref blockDataRef, ZigZag.ZigZagOrder[k]);
if (coef != 0) if (coef != 0)
{ {
buffer.CheckBits(); buffer.CheckBits();
@ -643,7 +672,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
if ((s != 0) && (k < 64)) if ((s != 0) && (k < 64))
{ {
Unsafe.Add(ref blockDataRef, zigzag[k]) = (short)s; Unsafe.Add(ref blockDataRef, ZigZag.ZigZagOrder[k]) = (short)s;
} }
} }
} }
@ -652,7 +681,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
for (; k <= end; k++) for (; k <= end; k++)
{ {
ref short coef = ref Unsafe.Add(ref blockDataRef, zigzag[k]); ref short coef = ref Unsafe.Add(ref blockDataRef, ZigZag.ZigZagOrder[k]);
if (coef != 0) if (coef != 0)
{ {
@ -714,5 +743,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
return false; return false;
} }
/// <summary>
/// Build the huffman table using code lengths and code values.
/// </summary>
/// <param name="type">Table type.</param>
/// <param name="index">Table index.</param>
/// <param name="codeLengths">Code lengths.</param>
/// <param name="values">Code values.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void BuildHuffmanTable(int type, int index, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
{
HuffmanTable[] tables = type == 0 ? this.dcHuffmanTables : this.acHuffmanTables;
tables[index] = new HuffmanTable(codeLengths, values);
}
} }
} }

17
src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs

@ -11,33 +11,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
internal interface IRawJpegData : IDisposable internal interface IRawJpegData : IDisposable
{ {
/// <summary>
/// Gets the image size in pixels.
/// </summary>
Size ImageSizeInPixels { get; }
/// <summary>
/// Gets the number of components.
/// </summary>
int ComponentCount { get; }
/// <summary> /// <summary>
/// Gets the color space /// Gets the color space
/// </summary> /// </summary>
JpegColorSpace ColorSpace { get; } JpegColorSpace ColorSpace { get; }
/// <summary>
/// Gets the number of bits used for precision.
/// </summary>
int Precision { get; }
/// <summary> /// <summary>
/// Gets the components. /// Gets the components.
/// </summary> /// </summary>
IJpegComponent[] Components { get; } IJpegComponent[] Components { get; }
/// <summary> /// <summary>
/// Gets the quantization tables, in zigzag order. /// Gets the quantization tables, in natural order.
/// </summary> /// </summary>
Block8x8F[] QuantizationTables { get; } Block8x8F[] QuantizationTables { get; }
} }

32
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs

@ -19,14 +19,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
public Block8x8F SourceBlock; public Block8x8F SourceBlock;
/// <summary> /// <summary>
/// Temporal block 1 to store intermediate and/or final computation results. /// Temporal block to store intermediate computation results.
/// </summary> /// </summary>
public Block8x8F WorkspaceBlock1; public Block8x8F WorkspaceBlock;
/// <summary>
/// Temporal block 2 to store intermediate and/or final computation results.
/// </summary>
public Block8x8F WorkspaceBlock2;
/// <summary> /// <summary>
/// The quantization table as <see cref="Block8x8F"/>. /// The quantization table as <see cref="Block8x8F"/>.
@ -38,11 +33,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
private Size subSamplingDivisors; private Size subSamplingDivisors;
/// <summary>
/// Defines the maximum value derived from the bitdepth.
/// </summary>
private readonly int maximumValue;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegBlockPostProcessor"/> struct. /// Initializes a new instance of the <see cref="JpegBlockPostProcessor"/> struct.
/// </summary> /// </summary>
@ -51,13 +41,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
public JpegBlockPostProcessor(IRawJpegData decoder, IJpegComponent component) public JpegBlockPostProcessor(IRawJpegData decoder, IJpegComponent component)
{ {
int qtIndex = component.QuantizationTableIndex; int qtIndex = component.QuantizationTableIndex;
this.DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]); this.DequantiazationTable = decoder.QuantizationTables[qtIndex];
this.subSamplingDivisors = component.SubSamplingDivisors; this.subSamplingDivisors = component.SubSamplingDivisors;
this.maximumValue = (int)MathF.Pow(2, decoder.Precision) - 1;
this.SourceBlock = default; this.SourceBlock = default;
this.WorkspaceBlock1 = default; this.WorkspaceBlock = default;
this.WorkspaceBlock2 = default;
} }
/// <summary> /// <summary>
@ -77,20 +65,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
int destAreaStride, int destAreaStride,
float maximumValue) float maximumValue)
{ {
ref Block8x8F b = ref this.SourceBlock; ref Block8x8F block = ref this.SourceBlock;
b.LoadFrom(ref sourceBlock); block.LoadFrom(ref sourceBlock);
// Dequantize: // Dequantize:
b.MultiplyInPlace(ref this.DequantiazationTable); block.MultiplyInPlace(ref this.DequantiazationTable);
FastFloatingPointDCT.TransformIDCT(ref b, ref this.WorkspaceBlock1, ref this.WorkspaceBlock2); FastFloatingPointDCT.TransformIDCT(ref block, ref this.WorkspaceBlock);
// To conform better to libjpeg we actually NEED TO loose precision here. // To conform better to libjpeg we actually NEED TO loose precision here.
// This is because they store blocks as Int16 between all the operations. // This is because they store blocks as Int16 between all the operations.
// To be "more accurate", we need to emulate this by rounding! // To be "more accurate", we need to emulate this by rounding!
this.WorkspaceBlock1.NormalizeColorsAndRoundInPlace(maximumValue); block.NormalizeColorsAndRoundInPlace(maximumValue);
this.WorkspaceBlock1.ScaledCopyTo( block.ScaledCopyTo(
ref destAreaOrigin, ref destAreaOrigin,
destAreaStride, destAreaStride,
this.subSamplingDivisors.Width, this.subSamplingDivisors.Width,

33
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
@ -32,7 +31,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
if (quantizationTableIndex > 3) if (quantizationTableIndex > 3)
{ {
JpegThrowHelper.ThrowBadQuantizationTable(); JpegThrowHelper.ThrowBadQuantizationTableIndex(quantizationTableIndex);
} }
this.QuantizationTableIndex = quantizationTableIndex; this.QuantizationTableIndex = quantizationTableIndex;
@ -106,31 +105,43 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.SpectralBlocks = null; this.SpectralBlocks = null;
} }
public void Init() /// <summary>
/// Initializes component for future buffers initialization.
/// </summary>
/// <param name="maxSubFactorH">Maximal horizontal subsampling factor among all the components.</param>
/// <param name="maxSubFactorV">Maximal vertical subsampling factor among all the components.</param>
public void Init(int maxSubFactorH, int maxSubFactorV)
{ {
this.WidthInBlocks = (int)MathF.Ceiling( this.WidthInBlocks = (int)MathF.Ceiling(
MathF.Ceiling(this.Frame.SamplesPerLine / 8F) * this.HorizontalSamplingFactor / this.Frame.MaxHorizontalFactor); MathF.Ceiling(this.Frame.PixelWidth / 8F) * this.HorizontalSamplingFactor / maxSubFactorH);
this.HeightInBlocks = (int)MathF.Ceiling( this.HeightInBlocks = (int)MathF.Ceiling(
MathF.Ceiling(this.Frame.Scanlines / 8F) * this.VerticalSamplingFactor / this.Frame.MaxVerticalFactor); MathF.Ceiling(this.Frame.PixelHeight / 8F) * this.VerticalSamplingFactor / maxSubFactorV);
int blocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalSamplingFactor; int blocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalSamplingFactor;
int blocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalSamplingFactor; int blocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalSamplingFactor;
this.SizeInBlocks = new Size(blocksPerLineForMcu, blocksPerColumnForMcu); this.SizeInBlocks = new Size(blocksPerLineForMcu, blocksPerColumnForMcu);
JpegComponent c0 = this.Frame.Components[0]; this.SubSamplingDivisors = new Size(maxSubFactorH, maxSubFactorV).DivideBy(this.SamplingFactors);
this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors);
if (this.SubSamplingDivisors.Width == 0 || this.SubSamplingDivisors.Height == 0) if (this.SubSamplingDivisors.Width == 0 || this.SubSamplingDivisors.Height == 0)
{ {
JpegThrowHelper.ThrowBadSampling(); JpegThrowHelper.ThrowBadSampling();
} }
}
public void AllocateSpectral(bool fullScan)
{
if (this.SpectralBlocks != null)
{
// this method will be called each scan marker so we need to allocate only once
return;
}
int totalNumberOfBlocks = blocksPerColumnForMcu * (blocksPerLineForMcu + 1); int spectralAllocWidth = this.SizeInBlocks.Width;
int width = this.WidthInBlocks + 1; int spectralAllocHeight = fullScan ? this.SizeInBlocks.Height : this.VerticalSamplingFactor;
int height = totalNumberOfBlocks / width;
this.SpectralBlocks = this.memoryAllocator.Allocate2D<Block8x8>(width, height, AllocationOptions.Clean); this.SpectralBlocks = this.memoryAllocator.Allocate2D<Block8x8>(spectralAllocWidth, spectralAllocHeight, AllocationOptions.Clean);
} }
} }
} }

64
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs

@ -2,15 +2,12 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
/// <summary> /// <summary>
/// Encapsulates postprocessing data for one component for <see cref="JpegImagePostProcessor"/>. /// Encapsulates spectral data to rgba32 processing for one component.
/// </summary> /// </summary>
internal class JpegComponentPostProcessor : IDisposable internal class JpegComponentPostProcessor : IDisposable
{ {
@ -24,26 +21,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
private readonly Size blockAreaSize; private readonly Size blockAreaSize;
/// <summary>
/// Jpeg frame instance containing required decoding metadata.
/// </summary>
private readonly JpegFrame frame;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegComponentPostProcessor"/> class. /// Initializes a new instance of the <see cref="JpegComponentPostProcessor"/> class.
/// </summary> /// </summary>
public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegImagePostProcessor imagePostProcessor, IJpegComponent component) public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component)
{ {
this.frame = frame;
this.Component = component; this.Component = component;
this.ImagePostProcessor = imagePostProcessor; this.RawJpeg = rawJpeg;
this.blockAreaSize = this.Component.SubSamplingDivisors * 8; this.blockAreaSize = this.Component.SubSamplingDivisors * 8;
this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>( this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>(
imagePostProcessor.PostProcessorBufferSize.Width, postProcessorBufferSize.Width,
imagePostProcessor.PostProcessorBufferSize.Height, postProcessorBufferSize.Height,
this.blockAreaSize.Height); this.blockAreaSize.Height);
this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height; this.BlockRowsPerStep = postProcessorBufferSize.Height / 8 / this.Component.SubSamplingDivisors.Height;
} }
/// <summary> public IRawJpegData RawJpeg { get; }
/// Gets the <see cref="JpegImagePostProcessor"/>
/// </summary>
public JpegImagePostProcessor ImagePostProcessor { get; }
/// <summary> /// <summary>
/// Gets the <see cref="Component"/> /// Gets the <see cref="Component"/>
@ -66,26 +67,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
public int BlockRowsPerStep { get; } public int BlockRowsPerStep { get; }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose() => this.ColorBuffer.Dispose();
{
this.ColorBuffer.Dispose();
}
/// <summary> /// <summary>
/// Invoke <see cref="JpegBlockPostProcessor"/> for <see cref="BlockRowsPerStep"/> block rows, copy the result into <see cref="ColorBuffer"/>. /// Invoke <see cref="JpegBlockPostProcessor"/> for <see cref="BlockRowsPerStep"/> block rows, copy the result into <see cref="ColorBuffer"/>.
/// </summary> /// </summary>
public void CopyBlocksToColorBuffer() public void CopyBlocksToColorBuffer(int step)
{ {
var blockPp = new JpegBlockPostProcessor(this.ImagePostProcessor.RawJpeg, this.Component); Buffer2D<Block8x8> spectralBuffer = this.Component.SpectralBlocks;
float maximumValue = MathF.Pow(2, this.ImagePostProcessor.RawJpeg.Precision) - 1;
var blockPp = new JpegBlockPostProcessor(this.RawJpeg, this.Component);
float maximumValue = this.frame.MaxColorChannelValue;
int destAreaStride = this.ColorBuffer.Width; int destAreaStride = this.ColorBuffer.Width;
int yBlockStart = step * this.BlockRowsPerStep;
for (int y = 0; y < this.BlockRowsPerStep; y++) for (int y = 0; y < this.BlockRowsPerStep; y++)
{ {
int yBlock = this.currentComponentRowInBlocks + y; int yBlock = yBlockStart + y;
if (yBlock >= this.SizeInBlocks.Height) if (yBlock >= spectralBuffer.Height)
{ {
break; break;
} }
@ -93,10 +96,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
int yBuffer = y * this.blockAreaSize.Height; int yBuffer = y * this.blockAreaSize.Height;
Span<float> colorBufferRow = this.ColorBuffer.GetRowSpan(yBuffer); Span<float> colorBufferRow = this.ColorBuffer.GetRowSpan(yBuffer);
Span<Block8x8> blockRow = this.Component.SpectralBlocks.GetRowSpan(yBlock); Span<Block8x8> blockRow = spectralBuffer.GetRowSpan(yBlock);
// see: https://github.com/SixLabors/ImageSharp/issues/824 // see: https://github.com/SixLabors/ImageSharp/issues/824
int widthInBlocks = Math.Min(this.Component.SpectralBlocks.Width, this.SizeInBlocks.Width); int widthInBlocks = Math.Min(spectralBuffer.Width, this.SizeInBlocks.Width);
for (int xBlock = 0; xBlock < widthInBlocks; xBlock++) for (int xBlock = 0; xBlock < widthInBlocks; xBlock++)
{ {
@ -107,7 +110,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
blockPp.ProcessBlockColorsInto(ref block, ref destAreaOrigin, destAreaStride, maximumValue); blockPp.ProcessBlockColorsInto(ref block, ref destAreaOrigin, destAreaStride, maximumValue);
} }
} }
}
public void ClearSpectralBuffers()
{
Buffer2D<Block8x8> spectralBlocks = this.Component.SpectralBlocks;
for (int i = 0; i < spectralBlocks.Height; i++)
{
spectralBlocks.GetRowSpan(i).Clear();
}
}
public void CopyBlocksToColorBuffer()
{
this.CopyBlocksToColorBuffer(this.currentComponentRowInBlocks);
this.currentComponentRowInBlocks += this.BlockRowsPerStep; this.currentComponentRowInBlocks += this.BlockRowsPerStep;
} }
} }

91
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs

@ -10,35 +10,67 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
internal sealed class JpegFrame : IDisposable internal sealed class JpegFrame : IDisposable
{ {
public JpegFrame(JpegFileMarker sofMarker, byte precision, int width, int height, byte componentCount)
{
this.Extended = sofMarker.Marker == JpegConstants.Markers.SOF1;
this.Progressive = sofMarker.Marker == JpegConstants.Markers.SOF2;
this.Precision = precision;
this.MaxColorChannelValue = MathF.Pow(2, precision) - 1;
this.PixelWidth = width;
this.PixelHeight = height;
this.ComponentCount = componentCount;
}
/// <summary>
/// Gets a value indicating whether the frame uses the extended specification.
/// </summary>
public bool Extended { get; private set; }
/// <summary>
/// Gets a value indicating whether the frame uses the progressive specification.
/// </summary>
public bool Progressive { get; private set; }
/// <summary>
/// Gets or sets a value indicating whether the frame is encoded using multiple scans (SOS markers).
/// </summary>
/// <remarks>
/// This is true for progressive and baseline non-interleaved images.
/// </remarks>
public bool MultiScan { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the frame uses the extended specification. /// Gets the precision.
/// </summary> /// </summary>
public bool Extended { get; set; } public byte Precision { get; private set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the frame uses the progressive specification. /// Gets the maximum color value derived from <see cref="Precision"/>.
/// </summary> /// </summary>
public bool Progressive { get; set; } public float MaxColorChannelValue { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the precision. /// Gets the number of pixel per row.
/// </summary> /// </summary>
public byte Precision { get; set; } public int PixelHeight { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the number of scanlines within the frame. /// Gets the number of pixels per line.
/// </summary> /// </summary>
public int Scanlines { get; set; } public int PixelWidth { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the number of samples per scanline. /// Gets the pixel size of the image.
/// </summary> /// </summary>
public int SamplesPerLine { get; set; } public Size PixelSize => new Size(this.PixelWidth, this.PixelHeight);
/// <summary> /// <summary>
/// Gets or sets the number of components within a frame. In progressive frames this value can range from only 1 to 4. /// Gets the number of components within a frame.
/// </summary> /// </summary>
public byte ComponentCount { get; set; } public byte ComponentCount { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the component id collection. /// Gets or sets the component id collection.
@ -57,24 +89,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
public JpegComponent[] Components { get; set; } public JpegComponent[] Components { get; set; }
/// <summary> /// <summary>
/// Gets or sets the maximum horizontal sampling factor. /// Gets or sets the number of MCU's per line.
/// </summary> /// </summary>
public int MaxHorizontalFactor { get; set; } public int McusPerLine { get; set; }
/// <summary> /// <summary>
/// Gets or sets the maximum vertical sampling factor. /// Gets or sets the number of MCU's per column.
/// </summary> /// </summary>
public int MaxVerticalFactor { get; set; } public int McusPerColumn { get; set; }
/// <summary> /// <summary>
/// Gets or sets the number of MCU's per line. /// Gets the mcu size of the image.
/// </summary> /// </summary>
public int McusPerLine { get; set; } public Size McuSize => new Size(this.McusPerLine, this.McusPerColumn);
/// <summary> /// <summary>
/// Gets or sets the number of MCU's per column. /// Gets the color depth, in number of bits per pixel.
/// </summary> /// </summary>
public int McusPerColumn { get; set; } public int BitsPerPixel => this.ComponentCount * this.Precision;
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
@ -93,15 +125,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary> /// <summary>
/// Allocates the frame component blocks. /// Allocates the frame component blocks.
/// </summary> /// </summary>
public void InitComponents() /// <param name="maxSubFactorH">Maximal horizontal subsampling factor among all the components.</param>
/// <param name="maxSubFactorV">Maximal vertical subsampling factor among all the components.</param>
public void Init(int maxSubFactorH, int maxSubFactorV)
{ {
this.McusPerLine = (int)MathF.Ceiling(this.SamplesPerLine / 8F / this.MaxHorizontalFactor); this.McusPerLine = (int)Numerics.DivideCeil((uint)this.PixelWidth, (uint)maxSubFactorH * 8);
this.McusPerColumn = (int)MathF.Ceiling(this.Scanlines / 8F / this.MaxVerticalFactor); this.McusPerColumn = (int)Numerics.DivideCeil((uint)this.PixelHeight, (uint)maxSubFactorV * 8);
for (int i = 0; i < this.ComponentCount; i++) for (int i = 0; i < this.ComponentCount; i++)
{ {
JpegComponent component = this.Components[i]; JpegComponent component = this.Components[i];
component.Init(); component.Init(maxSubFactorH, maxSubFactorV);
}
}
public void AllocateComponents(bool fullScan)
{
for (int i = 0; i < this.ComponentCount; i++)
{
JpegComponent component = this.Components[i];
component.AllocateSpectral(fullScan);
} }
} }
} }

181
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs

@ -1,181 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Numerics;
using System.Threading;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using JpegColorConverter = SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters.JpegColorConverter;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Encapsulates the execution od post-processing algorithms to be applied on a <see cref="IRawJpegData"/> to produce a valid <see cref="Image{TPixel}"/>: <br/>
/// (1) Dequantization <br/>
/// (2) IDCT <br/>
/// (3) Color conversion form one of the <see cref="JpegColorSpace"/>-s into a <see cref="Vector4"/> buffer of RGBA values <br/>
/// (4) Packing <see cref="Image{TPixel}"/> pixels from the <see cref="Vector4"/> buffer. <br/>
/// These operations are executed in <see cref="NumberOfPostProcessorSteps"/> steps.
/// <see cref="PixelRowsPerStep"/> image rows are converted in one step,
/// which means that size of the allocated memory is limited (does not depend on <see cref="ImageFrame.Height"/>).
/// </summary>
internal class JpegImagePostProcessor : IDisposable
{
private readonly Configuration configuration;
/// <summary>
/// The number of block rows to be processed in one Step.
/// </summary>
public const int BlockRowsPerStep = 4;
/// <summary>
/// The number of image pixel rows to be processed in one step.
/// </summary>
public const int PixelRowsPerStep = 4 * 8;
/// <summary>
/// Temporal buffer to store a row of colors.
/// </summary>
private readonly IMemoryOwner<Vector4> rgbaBuffer;
/// <summary>
/// The <see cref="JpegColorConverter"/> corresponding to the current <see cref="JpegColorSpace"/> determined by <see cref="IRawJpegData.ColorSpace"/>.
/// </summary>
private readonly JpegColorConverter colorConverter;
/// <summary>
/// Initializes a new instance of the <see cref="JpegImagePostProcessor"/> class.
/// </summary>
/// <param name="configuration">The <see cref="Configuration"/> to configure internal operations.</param>
/// <param name="rawJpeg">The <see cref="IRawJpegData"/> representing the uncompressed spectral Jpeg data</param>
public JpegImagePostProcessor(Configuration configuration, IRawJpegData rawJpeg)
{
this.configuration = configuration;
this.RawJpeg = rawJpeg;
IJpegComponent c0 = rawJpeg.Components[0];
this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / BlockRowsPerStep;
this.PostProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, PixelRowsPerStep);
MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
this.ComponentProcessors = new JpegComponentPostProcessor[rawJpeg.Components.Length];
for (int i = 0; i < rawJpeg.Components.Length; i++)
{
this.ComponentProcessors[i] = new JpegComponentPostProcessor(memoryAllocator, this, rawJpeg.Components[i]);
}
this.rgbaBuffer = memoryAllocator.Allocate<Vector4>(rawJpeg.ImageSizeInPixels.Width);
this.colorConverter = JpegColorConverter.GetConverter(rawJpeg.ColorSpace, rawJpeg.Precision);
}
/// <summary>
/// Gets the <see cref="JpegComponentPostProcessor"/> instances.
/// </summary>
public JpegComponentPostProcessor[] ComponentProcessors { get; }
/// <summary>
/// Gets the <see cref="IRawJpegData"/> to be processed.
/// </summary>
public IRawJpegData RawJpeg { get; }
/// <summary>
/// Gets the total number of post processor steps deduced from the height of the image and <see cref="PixelRowsPerStep"/>.
/// </summary>
public int NumberOfPostProcessorSteps { get; }
/// <summary>
/// Gets the size of the temporary buffers we need to allocate into <see cref="JpegComponentPostProcessor.ColorBuffer"/>.
/// </summary>
public Size PostProcessorBufferSize { get; }
/// <summary>
/// Gets the value of the counter that grows by each step by <see cref="PixelRowsPerStep"/>.
/// </summary>
public int PixelRowCounter { get; private set; }
/// <inheritdoc />
public void Dispose()
{
foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors)
{
cpp.Dispose();
}
this.rgbaBuffer.Dispose();
}
/// <summary>
/// Process all pixels into 'destination'. The image dimensions should match <see cref="RawJpeg"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="destination">The destination image</param>
/// <param name="cancellationToken">The token to request cancellation.</param>
public void PostProcess<TPixel>(ImageFrame<TPixel> destination, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
this.PixelRowCounter = 0;
if (this.RawJpeg.ImageSizeInPixels != destination.Size())
{
throw new ArgumentException("Input image is not of the size of the processed one!");
}
while (this.PixelRowCounter < this.RawJpeg.ImageSizeInPixels.Height)
{
cancellationToken.ThrowIfCancellationRequested();
this.DoPostProcessorStep(destination);
}
}
/// <summary>
/// Execute one step processing <see cref="PixelRowsPerStep"/> pixel rows into 'destination'.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="destination">The destination image.</param>
public void DoPostProcessorStep<TPixel>(ImageFrame<TPixel> destination)
where TPixel : unmanaged, IPixel<TPixel>
{
foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors)
{
cpp.CopyBlocksToColorBuffer();
}
this.ConvertColorsInto(destination);
this.PixelRowCounter += PixelRowsPerStep;
}
/// <summary>
/// Convert and copy <see cref="PixelRowsPerStep"/> row of colors into 'destination' starting at row <see cref="PixelRowCounter"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="destination">The destination image</param>
private void ConvertColorsInto<TPixel>(ImageFrame<TPixel> destination)
where TPixel : unmanaged, IPixel<TPixel>
{
int maxY = Math.Min(destination.Height, this.PixelRowCounter + PixelRowsPerStep);
var buffers = new Buffer2D<float>[this.ComponentProcessors.Length];
for (int i = 0; i < this.ComponentProcessors.Length; i++)
{
buffers[i] = this.ComponentProcessors[i].ColorBuffer;
}
for (int yy = this.PixelRowCounter; yy < maxY; yy++)
{
int y = yy - this.PixelRowCounter;
var values = new JpegColorConverter.ComponentValues(buffers, y);
this.colorConverter.ConvertToRgba(values, this.rgbaBuffer.GetSpan());
Span<TPixel> destRow = destination.GetPixelRowSpan(yy);
// TODO: Investigate if slicing is actually necessary
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, this.rgbaBuffer.GetSpan().Slice(0, destRow.Length), destRow);
}
}
}
}

144
src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs

@ -1,144 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Provides methods to evaluate the quality of an image.
/// Ported from <see href="https://github.com/ImageMagick/ImageMagick/blob/f362c02083d27211b913c6e44794f0ac6edaf2bd/coders/jpeg.c#L855"/>
/// </summary>
internal static class QualityEvaluator
{
private static readonly int[] Hash = new int[101]
{
1020, 1015, 932, 848, 780, 735, 702, 679, 660, 645,
632, 623, 613, 607, 600, 594, 589, 585, 581, 571,
555, 542, 529, 514, 494, 474, 457, 439, 424, 410,
397, 386, 373, 364, 351, 341, 334, 324, 317, 309,
299, 294, 287, 279, 274, 267, 262, 257, 251, 247,
243, 237, 232, 227, 222, 217, 213, 207, 202, 198,
192, 188, 183, 177, 173, 168, 163, 157, 153, 148,
143, 139, 132, 128, 125, 119, 115, 108, 104, 99,
94, 90, 84, 79, 74, 70, 64, 59, 55, 49,
45, 40, 34, 30, 25, 20, 15, 11, 6, 4,
0
};
private static readonly int[] Sums = new int[101]
{
32640, 32635, 32266, 31495, 30665, 29804, 29146, 28599, 28104,
27670, 27225, 26725, 26210, 25716, 25240, 24789, 24373, 23946,
23572, 22846, 21801, 20842, 19949, 19121, 18386, 17651, 16998,
16349, 15800, 15247, 14783, 14321, 13859, 13535, 13081, 12702,
12423, 12056, 11779, 11513, 11135, 10955, 10676, 10392, 10208,
9928, 9747, 9564, 9369, 9193, 9017, 8822, 8639, 8458,
8270, 8084, 7896, 7710, 7527, 7347, 7156, 6977, 6788,
6607, 6422, 6236, 6054, 5867, 5684, 5495, 5305, 5128,
4945, 4751, 4638, 4442, 4248, 4065, 3888, 3698, 3509,
3326, 3139, 2957, 2775, 2586, 2405, 2216, 2037, 1846,
1666, 1483, 1297, 1109, 927, 735, 554, 375, 201,
128, 0
};
private static readonly int[] Hash1 = new int[101]
{
510, 505, 422, 380, 355, 338, 326, 318, 311, 305,
300, 297, 293, 291, 288, 286, 284, 283, 281, 280,
279, 278, 277, 273, 262, 251, 243, 233, 225, 218,
211, 205, 198, 193, 186, 181, 177, 172, 168, 164,
158, 156, 152, 148, 145, 142, 139, 136, 133, 131,
129, 126, 123, 120, 118, 115, 113, 110, 107, 105,
102, 100, 97, 94, 92, 89, 87, 83, 81, 79,
76, 74, 70, 68, 66, 63, 61, 57, 55, 52,
50, 48, 44, 42, 39, 37, 34, 31, 29, 26,
24, 21, 18, 16, 13, 11, 8, 6, 3, 2,
0
};
private static readonly int[] Sums1 = new int[101]
{
16320, 16315, 15946, 15277, 14655, 14073, 13623, 13230, 12859,
12560, 12240, 11861, 11456, 11081, 10714, 10360, 10027, 9679,
9368, 9056, 8680, 8331, 7995, 7668, 7376, 7084, 6823,
6562, 6345, 6125, 5939, 5756, 5571, 5421, 5240, 5086,
4976, 4829, 4719, 4616, 4463, 4393, 4280, 4166, 4092,
3980, 3909, 3835, 3755, 3688, 3621, 3541, 3467, 3396,
3323, 3247, 3170, 3096, 3021, 2952, 2874, 2804, 2727,
2657, 2583, 2509, 2437, 2362, 2290, 2211, 2136, 2068,
1996, 1915, 1858, 1773, 1692, 1620, 1552, 1477, 1398,
1326, 1251, 1179, 1109, 1031, 961, 884, 814, 736,
667, 592, 518, 441, 369, 292, 221, 151, 86,
64, 0
};
/// <summary>
/// Returns an estimated quality of the image based on the quantization tables.
/// </summary>
/// <param name="quantizationTables">The quantization tables.</param>
/// <returns>The <see cref="int"/>.</returns>
public static int EstimateQuality(Block8x8F[] quantizationTables)
{
int quality = 75;
float sum = 0;
for (int i = 0; i < quantizationTables.Length; i++)
{
ref Block8x8F qTable = ref quantizationTables[i];
if (!qTable.Equals(default))
{
for (int j = 0; j < Block8x8F.Size; j++)
{
sum += qTable[j];
}
}
}
ref Block8x8F qTable0 = ref quantizationTables[0];
ref Block8x8F qTable1 = ref quantizationTables[1];
if (!qTable0.Equals(default))
{
if (!qTable1.Equals(default))
{
quality = (int)(qTable0[2]
+ qTable0[53]
+ qTable1[0]
+ qTable1[Block8x8F.Size - 1]);
for (int i = 0; i < 100; i++)
{
if (quality < Hash[i] && sum < Sums[i])
{
continue;
}
if (((quality <= Hash[i]) && (sum <= Sums[i])) || (i >= 50))
{
return i + 1;
}
}
}
else
{
quality = (int)(qTable0[2] + qTable0[53]);
for (int i = 0; i < 100; i++)
{
if (quality < Hash1[i] && sum < Sums1[i])
{
continue;
}
if (((quality <= Hash1[i]) && (sum <= Sums1[i])) || (i >= 50))
{
return i + 1;
}
}
}
}
return quality;
}
}
}

44
src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs

@ -0,0 +1,44 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Converter used to convert jpeg spectral data.
/// </summary>
/// <remarks>
/// This is tightly coupled with <see cref="HuffmanScanDecoder"/> and <see cref="JpegDecoderCore"/>.
/// </remarks>
internal abstract class SpectralConverter
{
/// <summary>
/// Injects jpeg image decoding metadata.
/// </summary>
/// <remarks>
/// This is guaranteed to be called only once at SOF marker by <see cref="HuffmanScanDecoder"/>.
/// </remarks>
/// <param name="frame"><see cref="JpegFrame"/> instance containing decoder-specific parameters.</param>
/// <param name="jpegData"><see cref="IRawJpegData"/> instance containing decoder-specific parameters.</param>
public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData);
/// <summary>
/// Called once per spectral stride for each component in <see cref="HuffmanScanDecoder"/>.
/// This is called only for baseline interleaved jpegs.
/// </summary>
/// <remarks>
/// Spectral 'stride' doesn't particularly mean 'single stride'.
/// Actual stride height depends on the subsampling factor of the given component.
/// </remarks>
public abstract void ConvertStrideBaseline();
/// <summary>
/// Gets the color converter.
/// </summary>
/// <param name="frame">The jpeg frame with the color space to convert to.</param>
/// <param name="jpegData">The raw JPEG data.</param>
/// <returns>The color converter.</returns>
protected virtual JpegColorConverter GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision);
}
}

172
src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs

@ -0,0 +1,172 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Numerics;
using System.Threading;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
internal class SpectralConverter<TPixel> : SpectralConverter, IDisposable
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly Configuration configuration;
private readonly CancellationToken cancellationToken;
private JpegComponentPostProcessor[] componentProcessors;
private JpegColorConverter colorConverter;
// private IMemoryOwner<Vector4> rgbaBuffer;
private IMemoryOwner<byte> rgbBuffer;
private IMemoryOwner<TPixel> paddedProxyPixelRow;
private Buffer2D<TPixel> pixelBuffer;
private int blockRowsPerStep;
private int pixelRowsPerStep;
private int pixelRowCounter;
public SpectralConverter(Configuration configuration, CancellationToken cancellationToken)
{
this.configuration = configuration;
this.cancellationToken = cancellationToken;
}
private bool Converted => this.pixelRowCounter >= this.pixelBuffer.Height;
public Buffer2D<TPixel> GetPixelBuffer()
{
if (!this.Converted)
{
int steps = (int)Math.Ceiling(this.pixelBuffer.Height / (float)this.pixelRowsPerStep);
for (int step = 0; step < steps; step++)
{
this.cancellationToken.ThrowIfCancellationRequested();
this.ConvertNextStride(step);
}
}
return this.pixelBuffer;
}
/// <inheritdoc/>
public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData)
{
MemoryAllocator allocator = this.configuration.MemoryAllocator;
// iteration data
IJpegComponent c0 = frame.Components[0];
const int blockPixelHeight = 8;
this.blockRowsPerStep = c0.SamplingFactors.Height;
this.pixelRowsPerStep = this.blockRowsPerStep * blockPixelHeight;
// pixel buffer for resulting image
this.pixelBuffer = allocator.Allocate2D<TPixel>(frame.PixelWidth, frame.PixelHeight);
this.paddedProxyPixelRow = allocator.Allocate<TPixel>(frame.PixelWidth + 3);
// component processors from spectral to Rgba32
var postProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, this.pixelRowsPerStep);
this.componentProcessors = new JpegComponentPostProcessor[frame.Components.Length];
for (int i = 0; i < this.componentProcessors.Length; i++)
{
this.componentProcessors[i] = new JpegComponentPostProcessor(allocator, frame, jpegData, postProcessorBufferSize, frame.Components[i]);
}
// single 'stride' rgba32 buffer for conversion between spectral and TPixel
// this.rgbaBuffer = allocator.Allocate<Vector4>(frame.PixelWidth);
this.rgbBuffer = allocator.Allocate<byte>(frame.PixelWidth * 3);
// color converter from Rgba32 to TPixel
this.colorConverter = this.GetColorConverter(frame, jpegData);
}
/// <inheritdoc/>
public override void ConvertStrideBaseline()
{
// Convert next pixel stride using single spectral `stride'
// Note that zero passing eliminates the need of virtual call from JpegComponentPostProcessor
this.ConvertNextStride(spectralStep: 0);
// Clear spectral stride - this is VERY important as jpeg possibly won't fill entire buffer each stride
// Which leads to decoding artifacts
// Note that this code clears all buffers of the post processors, it's their responsibility to allocate only single stride
foreach (JpegComponentPostProcessor cpp in this.componentProcessors)
{
cpp.ClearSpectralBuffers();
}
}
public void Dispose()
{
if (this.componentProcessors != null)
{
foreach (JpegComponentPostProcessor cpp in this.componentProcessors)
{
cpp.Dispose();
}
}
this.rgbBuffer?.Dispose();
this.paddedProxyPixelRow?.Dispose();
}
private void ConvertNextStride(int spectralStep)
{
int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep);
var buffers = new Buffer2D<float>[this.componentProcessors.Length];
for (int i = 0; i < this.componentProcessors.Length; i++)
{
this.componentProcessors[i].CopyBlocksToColorBuffer(spectralStep);
buffers[i] = this.componentProcessors[i].ColorBuffer;
}
int width = this.pixelBuffer.Width;
for (int yy = this.pixelRowCounter; yy < maxY; yy++)
{
int y = yy - this.pixelRowCounter;
var values = new JpegColorConverter.ComponentValues(buffers, y);
this.colorConverter.ConvertToRgbInplace(values);
values = values.Slice(0, width); // slice away Jpeg padding
Span<byte> r = this.rgbBuffer.Slice(0, width);
Span<byte> g = this.rgbBuffer.Slice(width, width);
Span<byte> b = this.rgbBuffer.Slice(width * 2, width);
SimdUtils.NormalizedFloatToByteSaturate(values.Component0, r);
SimdUtils.NormalizedFloatToByteSaturate(values.Component1, g);
SimdUtils.NormalizedFloatToByteSaturate(values.Component2, b);
// PackFromRgbPlanes expects the destination to be padded, so try to get padded span containing extra elements from the next row.
// If we can't get such a padded row because we are on a MemoryGroup boundary or at the last row,
// pack pixels to a temporary, padded proxy buffer, then copy the relevant values to the destination row.
if (this.pixelBuffer.TryGetPaddedRowSpan(yy, 3, out Span<TPixel> destRow))
{
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.configuration, r, g, b, destRow);
}
else
{
Span<TPixel> proxyRow = this.paddedProxyPixelRow.GetSpan();
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.configuration, r, g, b, proxyRow);
proxyRow.Slice(0, width).CopyTo(this.pixelBuffer.GetRowSpan(yy));
}
}
this.pixelRowCounter += this.pixelRowsPerStep;
}
}
}

29
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs

@ -1,14 +1,29 @@
// 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.Components.Encoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{ {
/// <summary> /// <summary>
/// A compiled look-up table representation of a huffmanSpec. /// A compiled look-up table representation of a huffmanSpec.
/// Each value maps to a uint32 of which the 8 most significant bits hold the
/// codeword size in bits and the 24 least significant bits hold the codeword.
/// The maximum codeword size is 16 bits. /// The maximum codeword size is 16 bits.
/// </summary> /// </summary>
/// <remarks>
/// <para>
/// Each value maps to a int32 of which the 24 most significant bits hold the
/// codeword in bits and the 8 least significant bits hold the codeword size.
/// </para>
/// <para>
/// Code value occupies 24 most significant bits as integer value.
/// This value is shifted to the MSB position for performance reasons.
/// For example, decimal value 10 is stored like this:
/// <code>
/// MSB LSB
/// 1010 0000 00000000 00000000 | 00000100
/// </code>
/// This was done to eliminate extra binary shifts in the encoder.
/// While code length is represented as 8 bit integer value
/// </para>
/// </remarks>
internal readonly struct HuffmanLut internal readonly struct HuffmanLut
{ {
/// <summary> /// <summary>
@ -44,17 +59,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
} }
} }
this.Values = new uint[maxValue + 1]; this.Values = new int[maxValue + 1];
int code = 0; int code = 0;
int k = 0; int k = 0;
for (int i = 0; i < spec.Count.Length; i++) for (int i = 0; i < spec.Count.Length; i++)
{ {
int bits = (i + 1) << 24; int len = i + 1;
for (int j = 0; j < spec.Count[i]; j++) for (int j = 0; j < spec.Count[i]; j++)
{ {
this.Values[spec.Values[k]] = (uint)(bits | code); this.Values[spec.Values[k]] = len | (code << (32 - len));
code++; code++;
k++; k++;
} }
@ -66,6 +81,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary> /// <summary>
/// Gets the collection of huffman values. /// Gets the collection of huffman values.
/// </summary> /// </summary>
public uint[] Values { get; } public int[] Values { get; }
} }
} }

689
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs

@ -0,0 +1,689 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
internal class HuffmanScanEncoder
{
/// <summary>
/// Maximum number of bytes encoded jpeg 8x8 block can occupy.
/// It's highly unlikely for block to occupy this much space - it's a theoretical limit.
/// </summary>
/// <remarks>
/// Where 16 is maximum huffman code binary length according to itu
/// specs. 10 is maximum value binary length, value comes from discrete
/// cosine tranform with value range: [-1024..1023]. Block stores
/// 8x8 = 64 values thus multiplication by 64. Then divided by 8 to get
/// the number of bytes. This value is then multiplied by
/// <see cref="MaxBytesPerBlockMultiplier"/> for performance reasons.
/// </remarks>
private const int MaxBytesPerBlock = (16 + 10) * 64 / 8 * MaxBytesPerBlockMultiplier;
/// <summary>
/// Multiplier used within cache buffers size calculation.
/// </summary>
/// <remarks>
/// <para>
/// Theoretically, <see cref="MaxBytesPerBlock"/> bytes buffer can fit
/// exactly one minimal coding unit. In reality, coding blocks occupy much
/// less space than the theoretical maximum - this can be exploited.
/// If temporal buffer size is multiplied by at least 2, second half of
/// the resulting buffer will be used as an overflow 'guard' if next
/// block would occupy maximum number of bytes. While first half may fit
/// many blocks before needing to flush.
/// </para>
/// <para>
/// This is subject to change. This can be equal to 1 but recomended
/// value is 2 or even greater - futher benchmarking needed.
/// </para>
/// </remarks>
private const int MaxBytesPerBlockMultiplier = 2;
/// <summary>
/// <see cref="streamWriteBuffer"/> size multiplier.
/// </summary>
/// <remarks>
/// Jpeg specification requiers to insert 'stuff' bytes after each
/// 0xff byte value. Worst case scenarion is when all bytes are 0xff.
/// While it's highly unlikely (if not impossible) to get such
/// combination, it's theoretically possible so buffer size must be guarded.
/// </remarks>
private const int OutputBufferLengthMultiplier = 2;
/// <summary>
/// Compiled huffman tree to encode given values.
/// </summary>
/// <remarks>Yields codewords by index consisting of [run length | bitsize].</remarks>
private HuffmanLut[] huffmanTables;
/// <summary>
/// Emitted bits 'micro buffer' before being transferred to the <see cref="emitBuffer"/>.
/// </summary>
private uint accumulatedBits;
/// <summary>
/// Buffer for temporal storage of huffman rle encoding bit data.
/// </summary>
/// <remarks>
/// Encoding bits are assembled to 4 byte unsigned integers and then copied to this buffer.
/// This process does NOT include inserting stuff bytes.
/// </remarks>
private readonly uint[] emitBuffer;
/// <summary>
/// Buffer for temporal storage which is then written to the output stream.
/// </summary>
/// <remarks>
/// Encoding bits from <see cref="emitBuffer"/> are copied to this byte buffer including stuff bytes.
/// </remarks>
private readonly byte[] streamWriteBuffer;
/// <summary>
/// Number of jagged bits stored in <see cref="accumulatedBits"/>
/// </summary>
private int bitCount;
private int emitWriteIndex;
private Block8x8 tempBlock;
/// <summary>
/// The output stream. All attempted writes after the first error become no-ops.
/// </summary>
private readonly Stream target;
/// <summary>
/// Initializes a new instance of the <see cref="HuffmanScanEncoder"/> class.
/// </summary>
/// <param name="blocksPerCodingUnit">Amount of encoded 8x8 blocks per single jpeg macroblock.</param>
/// <param name="outputStream">Output stream for saving encoded data.</param>
public HuffmanScanEncoder(int blocksPerCodingUnit, Stream outputStream)
{
int emitBufferByteLength = MaxBytesPerBlock * blocksPerCodingUnit;
this.emitBuffer = new uint[emitBufferByteLength / sizeof(uint)];
this.emitWriteIndex = this.emitBuffer.Length;
this.streamWriteBuffer = new byte[emitBufferByteLength * OutputBufferLengthMultiplier];
this.target = outputStream;
}
/// <summary>
/// Gets a value indicating whether <see cref="emitBuffer"/> is full
/// and must be flushed using <see cref="FlushToStream()"/>
/// before encoding next 8x8 coding block.
/// </summary>
private bool IsStreamFlushNeeded
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.emitWriteIndex < (uint)this.emitBuffer.Length / 2;
}
/// <summary>
/// Encodes the image with no subsampling.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee.</param>
/// <param name="chrominanceQuantTable">Chrominance quantization table provided by the callee.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
public void Encode444<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
FastFloatingPointDCT.AdjustToFDCT(ref luminanceQuantTable);
FastFloatingPointDCT.AdjustToFDCT(ref chrominanceQuantTable);
this.huffmanTables = HuffmanLut.TheHuffmanLut;
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
var pixelConverter = new YCbCrForwardConverter444<TPixel>(frame);
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(x, y, ref currentRows);
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
prevDCY,
ref pixelConverter.Y,
ref luminanceQuantTable);
prevDCCb = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCb,
ref pixelConverter.Cb,
ref chrominanceQuantTable);
prevDCCr = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCr,
ref pixelConverter.Cr,
ref chrominanceQuantTable);
if (this.IsStreamFlushNeeded)
{
this.FlushToStream();
}
}
}
this.FlushRemainingBytes();
}
/// <summary>
/// Encodes the image with subsampling. The Cb and Cr components are each subsampled
/// at a factor of 2 both horizontally and vertically.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee.</param>
/// <param name="chrominanceQuantTable">Chrominance quantization table provided by the callee.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
public void Encode420<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
FastFloatingPointDCT.AdjustToFDCT(ref luminanceQuantTable);
FastFloatingPointDCT.AdjustToFDCT(ref chrominanceQuantTable);
this.huffmanTables = HuffmanLut.TheHuffmanLut;
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
var pixelConverter = new YCbCrForwardConverter420<TPixel>(frame);
for (int y = 0; y < pixels.Height; y += 16)
{
cancellationToken.ThrowIfCancellationRequested();
for (int x = 0; x < pixels.Width; x += 16)
{
for (int i = 0; i < 2; i++)
{
int yOff = i * 8;
currentRows.Update(pixelBuffer, y + yOff);
pixelConverter.Convert(x, y, ref currentRows, i);
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
prevDCY,
ref pixelConverter.YLeft,
ref luminanceQuantTable);
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
prevDCY,
ref pixelConverter.YRight,
ref luminanceQuantTable);
}
prevDCCb = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCb,
ref pixelConverter.Cb,
ref chrominanceQuantTable);
prevDCCr = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCr,
ref pixelConverter.Cr,
ref chrominanceQuantTable);
if (this.IsStreamFlushNeeded)
{
this.FlushToStream();
}
}
}
this.FlushRemainingBytes();
}
/// <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="luminanceQuantTable">Luminance quantization table provided by the callee.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
public void EncodeGrayscale<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
FastFloatingPointDCT.AdjustToFDCT(ref luminanceQuantTable);
this.huffmanTables = HuffmanLut.TheHuffmanLut;
// 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 luminanceQuantTable);
if (this.IsStreamFlushNeeded)
{
this.FlushToStream();
}
}
}
this.FlushRemainingBytes();
}
/// <summary>
/// Encodes the image with no subsampling and keeps the pixel data as Rgb24.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="quantTable">Quantization table provided by the callee.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
public void EncodeRgb<TPixel>(Image<TPixel> pixels, ref Block8x8F quantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
FastFloatingPointDCT.AdjustToFDCT(ref quantTable);
this.huffmanTables = HuffmanLut.TheHuffmanLut;
// ReSharper disable once InconsistentNaming
int prevDCR = 0, prevDCG = 0, prevDCB = 0;
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
var pixelConverter = new RgbForwardConverter<TPixel>(frame);
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(x, y, ref currentRows);
prevDCR = this.WriteBlock(
QuantIndex.Luminance,
prevDCR,
ref pixelConverter.R,
ref quantTable);
prevDCG = this.WriteBlock(
QuantIndex.Luminance,
prevDCG,
ref pixelConverter.G,
ref quantTable);
prevDCB = this.WriteBlock(
QuantIndex.Luminance,
prevDCB,
ref pixelConverter.B,
ref quantTable);
if (this.IsStreamFlushNeeded)
{
this.FlushToStream();
}
}
}
this.FlushRemainingBytes();
}
/// <summary>
/// Writes a block of pixel data using the given quantization table,
/// returning the post-quantized DC value of the DCT-transformed block.
/// The block is in natural (not zig-zag) order.
/// </summary>
/// <param name="index">The quantization table index.</param>
/// <param name="prevDC">The previous DC value.</param>
/// <param name="block">Source block.</param>
/// <param name="quant">Quantization table.</param>
/// <returns>The <see cref="int"/>.</returns>
private int WriteBlock(
QuantIndex index,
int prevDC,
ref Block8x8F block,
ref Block8x8F quant)
{
ref Block8x8 spectralBlock = ref this.tempBlock;
// Shifting level from 0..255 to -128..127
block.AddInPlace(-128f);
// Discrete cosine transform
FastFloatingPointDCT.TransformFDCT(ref block);
// Quantization
Block8x8F.Quantize(ref block, ref spectralBlock, ref quant);
// Emit the DC delta.
int dc = spectralBlock[0];
this.EmitHuffRLE(this.huffmanTables[2 * (int)index].Values, 0, dc - prevDC);
// Emit the AC components.
int[] acHuffTable = this.huffmanTables[(2 * (int)index) + 1].Values;
nint lastValuableIndex = spectralBlock.GetLastNonZeroIndex();
int runLength = 0;
ref short blockRef = ref Unsafe.As<Block8x8, short>(ref spectralBlock);
for (nint zig = 1; zig <= lastValuableIndex; zig++)
{
const int zeroRun1 = 1 << 4;
const int zeroRun16 = 16 << 4;
int ac = Unsafe.Add(ref blockRef, zig);
if (ac == 0)
{
runLength += zeroRun1;
}
else
{
while (runLength >= zeroRun16)
{
this.EmitHuff(acHuffTable, 0xf0);
runLength -= zeroRun16;
}
this.EmitHuffRLE(acHuffTable, runLength, ac);
runLength = 0;
}
}
// if mcu block contains trailing zeros - we must write end of block (EOB) value indicating that current block is over
// this can be done for any number of trailing zeros, even when all 63 ac values are zero
// (Block8x8F.Size - 1) == 63 - last index of the mcu elements
if (lastValuableIndex != Block8x8F.Size - 1)
{
this.EmitHuff(acHuffTable, 0x00);
}
return dc;
}
/// <summary>
/// Emits the most significant count of bits to the buffer.
/// </summary>
/// <remarks>
/// <para>
/// Supports up to 32 count of bits but, generally speaking, jpeg
/// standard assures that there won't be more than 16 bits per single
/// value.
/// </para>
/// <para>
/// Emitting algorithm uses 3 intermediate buffers for caching before
/// writing to the stream:
/// <list type="number">
/// <item>
/// <term>uint32</term>
/// <description>
/// Bit buffer. Encoded spectral values can occupy up to 16 bits, bits
/// are assembled to whole bytes via this intermediate buffer.
/// </description>
/// </item>
/// <item>
/// <term>uint32[]</term>
/// <description>
/// Assembled bytes from uint32 buffer are saved into this buffer.
/// uint32 buffer values are saved using indices from the last to the first.
/// As bytes are saved to the memory as 4-byte packages endianness matters:
/// Jpeg stream is big-endian, indexing buffer bytes from the last index to the
/// first eliminates all operations to extract separate bytes. This only works for
/// little-endian machines (there are no known examples of big-endian users atm).
/// For big-endians this approach is slower due to the separate byte extraction.
/// </description>
/// </item>
/// <item>
/// <term>byte[]</term>
/// <description>
/// Byte buffer used only during <see cref="FlushToStream(int)"/> method.
/// </description>
/// </item>
/// </list>
/// </para>
/// </remarks>
/// <param name="bits">Bits to emit, must be shifted to the left.</param>
/// <param name="count">Bits count stored in the bits parameter.</param>
[MethodImpl(InliningOptions.ShortMethod)]
private void Emit(uint bits, int count)
{
this.accumulatedBits |= bits >> this.bitCount;
count += this.bitCount;
if (count >= 32)
{
this.emitBuffer[--this.emitWriteIndex] = this.accumulatedBits;
this.accumulatedBits = bits << (32 - this.bitCount);
count -= 32;
}
this.bitCount = count;
}
/// <summary>
/// Emits the given value with the given Huffman table.
/// </summary>
/// <param name="table">Huffman table.</param>
/// <param name="value">Value to encode.</param>
[MethodImpl(InliningOptions.ShortMethod)]
private void EmitHuff(int[] table, int value)
{
int x = table[value];
this.Emit((uint)x & 0xffff_ff00u, x & 0xff);
}
/// <summary>
/// Emits given value via huffman rle encoding.
/// </summary>
/// <param name="table">Huffman table.</param>
/// <param name="runLength">The number of preceding zeroes, preshifted by 4 to the left.</param>
/// <param name="value">Value to encode.</param>
[MethodImpl(InliningOptions.ShortMethod)]
private void EmitHuffRLE(int[] table, int runLength, int value)
{
DebugGuard.IsTrue((runLength & 0xf) == 0, $"{nameof(runLength)} parameter must be shifted to the left by 4 bits");
int a = value;
int b = value;
if (a < 0)
{
a = -value;
b = value - 1;
}
int valueLen = GetHuffmanEncodingLength((uint)a);
// Huffman prefix code
int huffPackage = table[runLength | valueLen];
int prefixLen = huffPackage & 0xff;
uint prefix = (uint)huffPackage & 0xffff_0000u;
// Actual encoded value
uint encodedValue = (uint)b << (32 - valueLen);
// Doing two binary shifts to get rid of leading 1's in negative value case
this.Emit(prefix | (encodedValue >> prefixLen), prefixLen + valueLen);
}
/// <summary>
/// Calculates how many minimum bits needed to store given value for Huffman jpeg encoding.
/// </summary>
/// <remarks>
/// This is an internal operation supposed to be used only in <see cref="HuffmanScanEncoder"/> class for jpeg encoding.
/// </remarks>
/// <param name="value">The value.</param>
[MethodImpl(InliningOptions.ShortMethod)]
internal static int GetHuffmanEncodingLength(uint value)
{
DebugGuard.IsTrue(value <= (1 << 16), "Huffman encoder is supposed to encode a value of 16bit size max");
#if SUPPORTS_BITOPERATIONS
// This should have been implemented as (BitOperations.Log2(value) + 1) as in non-intrinsic implementation
// But internal log2 is implemented like this: (31 - (int)Lzcnt.LeadingZeroCount(value))
// BitOperations.Log2 implementation also checks if input value is zero for the convention 0->0
// Lzcnt would return 32 for input value of 0 - no need to check that with branching
// Fallback code if Lzcnt is not supported still use if-check
// But most modern CPUs support this instruction so this should not be a problem
return 32 - BitOperations.LeadingZeroCount(value);
#else
// Ideally:
// if 0 - return 0 in this case
// else - return log2(value) + 1
//
// Hack based on input value constraint:
// We know that input values are guaranteed to be maximum 16 bit large for huffman encoding
// We can safely shift input value for one bit -> log2(value << 1)
// Because of the 16 bit value constraint it won't overflow
// With that input value change we no longer need to add 1 before returning
// And this eliminates need to check if input value is zero - it is a standard convention which Log2SoftwareFallback adheres to
return Numerics.Log2(value << 1);
#endif
}
/// <summary>
/// General method for flushing cached spectral data bytes to
/// the ouput stream respecting stuff bytes.
/// </summary>
/// <remarks>
/// Bytes cached via <see cref="Emit"/> are stored in 4-bytes blocks
/// which makes this method endianness dependent.
/// </remarks>
[MethodImpl(InliningOptions.ShortMethod)]
private void FlushToStream(int endIndex)
{
Span<byte> emitBytes = MemoryMarshal.AsBytes(this.emitBuffer.AsSpan());
int writeIdx = 0;
int startIndex = emitBytes.Length - 1;
// Some platforms may fail to eliminate this if-else branching
// Even if it happens - buffer is flushed in big packs,
// branching overhead shouldn't be noticeable
if (BitConverter.IsLittleEndian)
{
// For little endian case bytes are ordered and can be
// safely written to the stream with stuff bytes
// First byte is cached on the most significant index
// so we are going from the end of the array to its beginning:
// ... [ double word #1 ] [ double word #0 ]
// ... [idx3|idx2|idx1|idx0] [idx3|idx2|idx1|idx0]
for (int i = startIndex; i >= endIndex; i--)
{
byte value = emitBytes[i];
this.streamWriteBuffer[writeIdx++] = value;
// Inserting stuff byte
if (value == 0xff)
{
this.streamWriteBuffer[writeIdx++] = 0x00;
}
}
}
else
{
// For big endian case bytes are ordered in 4-byte packs
// which are ordered like bytes in the little endian case by in 4-byte packs:
// ... [ double word #1 ] [ double word #0 ]
// ... [idx0|idx1|idx2|idx3] [idx0|idx1|idx2|idx3]
// So we must write each 4-bytes in 'natural order'
for (int i = startIndex; i >= endIndex; i -= 4)
{
// This loop is caused by the nature of underlying byte buffer
// implementation and indeed causes performace by somewhat 5%
// compared to little endian scenario
// Even with this performance drop this cached buffer implementation
// is faster than individually writing bytes using binary shifts and binary and(s)
for (int j = i - 3; j <= i; j++)
{
byte value = emitBytes[j];
this.streamWriteBuffer[writeIdx++] = value;
// Inserting stuff byte
if (value == 0xff)
{
this.streamWriteBuffer[writeIdx++] = 0x00;
}
}
}
}
this.target.Write(this.streamWriteBuffer, 0, writeIdx);
}
/// <summary>
/// Flushes spectral data bytes after encoding all channel blocks
/// in a single jpeg macroblock using <see cref="WriteBlock"/>.
/// </summary>
/// <remarks>
/// This must be called only if <see cref="IsStreamFlushNeeded"/> is true
/// only during the macroblocks encoding routine.
/// </remarks>
private void FlushToStream()
{
this.FlushToStream(this.emitWriteIndex * 4);
this.emitWriteIndex = this.emitBuffer.Length;
}
/// <summary>
/// Flushes final cached bits to the stream padding 1's to
/// complement full bytes.
/// </summary>
/// <remarks>
/// This must be called only once at the end of the encoding routine.
/// <see cref="IsStreamFlushNeeded"/> check is not needed.
/// </remarks>
[MethodImpl(InliningOptions.ShortMethod)]
private void FlushRemainingBytes()
{
// Padding all 4 bytes with 1's while not corrupting initial bits stored in accumulatedBits
// And writing only valuable count of bytes count we want to write to the output stream
int valuableBytesCount = (int)Numerics.DivideCeil((uint)this.bitCount, 8);
uint packedBytes = this.accumulatedBits | (uint.MaxValue >> this.bitCount);
this.emitBuffer[--this.emitWriteIndex] = packedBytes;
// Flush cached bytes to the output stream with padding bits
this.FlushToStream((this.emitWriteIndex * 4) - 4 + valuableBytesCount);
}
}
}

12
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.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.Components.Encoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
@ -24,9 +24,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
0, 0, 0 0, 0, 0
}, },
new byte[] new byte[]
{ {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
}), }),
// Luminance AC.
new HuffmanSpec( new HuffmanSpec(
new byte[] new byte[]
{ {
@ -60,6 +62,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
0xf9, 0xfa 0xf9, 0xfa
}), }),
// Chrominance DC.
new HuffmanSpec( new HuffmanSpec(
new byte[] new byte[]
{ {

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

@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
ref Block8x8F yBlock = ref this.Y; ref Block8x8F yBlock = ref this.Y;
ref L8 l8Start = ref l8Span[0]; ref L8 l8Start = ref l8Span[0];
for (int i = 0; i < 64; i++) for (int i = 0; i < Block8x8F.Size; i++)
{ {
ref L8 c = ref Unsafe.Add(ref l8Start, i); ref L8 c = ref Unsafe.Add(ref l8Start, i);
yBlock[i] = c.PackedValue; yBlock[i] = c.PackedValue;

8
src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs

@ -1,20 +1,20 @@
// 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.Components.Encoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{ {
/// <summary> /// <summary>
/// Enumerates the quantization tables /// Enumerates the quantization tables.
/// </summary> /// </summary>
internal enum QuantIndex internal enum QuantIndex
{ {
/// <summary> /// <summary>
/// The luminance quantization table index /// The luminance quantization table index.
/// </summary> /// </summary>
Luminance = 0, Luminance = 0,
/// <summary> /// <summary>
/// The chrominance quantization table index /// The chrominance quantization table index.
/// </summary> /// </summary>
Chrominance = 1, Chrominance = 1,
} }

114
src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs

@ -0,0 +1,114 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// On-stack worker struct to convert TPixel -> Rgb24 of 8x8 pixel blocks.
/// </summary>
/// <typeparam name="TPixel">The pixel type to work on.</typeparam>
internal ref struct RgbForwardConverter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Number of pixels processed per single <see cref="Convert(int, int, ref RowOctet{TPixel})"/> call
/// </summary>
private const int PixelsPerSample = 8 * 8;
/// <summary>
/// Total byte size of processed pixels converted from TPixel to <see cref="Rgb24"/>
/// </summary>
private const int RgbSpanByteSize = PixelsPerSample * 3;
/// <summary>
/// <see cref="Size"/> of sampling area from given frame pixel buffer.
/// </summary>
private static readonly Size SampleSize = new Size(8, 8);
/// <summary>
/// The Red component.
/// </summary>
public Block8x8F R;
/// <summary>
/// The Green component.
/// </summary>
public Block8x8F G;
/// <summary>
/// The Blue component.
/// </summary>
public Block8x8F B;
/// <summary>
/// Temporal 64-byte span to hold unconverted TPixel data.
/// </summary>
private readonly Span<TPixel> pixelSpan;
/// <summary>
/// Temporal 64-byte span to hold converted Rgb24 data.
/// </summary>
private readonly Span<Rgb24> rgbSpan;
/// <summary>
/// Sampled pixel buffer size.
/// </summary>
private readonly Size samplingAreaSize;
/// <summary>
/// <see cref="Configuration"/> for internal operations.
/// </summary>
private readonly Configuration config;
public RgbForwardConverter(ImageFrame<TPixel> frame)
{
this.R = default;
this.G = default;
this.B = default;
// temporal pixel buffers
this.pixelSpan = new TPixel[PixelsPerSample].AsSpan();
this.rgbSpan = MemoryMarshal.Cast<byte, Rgb24>(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan());
// frame data
this.samplingAreaSize = new Size(frame.Width, frame.Height);
this.config = frame.GetConfiguration();
}
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x, y) to Rgb24.
/// </summary>
public void Convert(int x, int y, ref RowOctet<TPixel> currentRows)
{
YCbCrForwardConverter<TPixel>.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize);
PixelOperations<TPixel>.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan);
ref Block8x8F redBlock = ref this.R;
ref Block8x8F greenBlock = ref this.G;
ref Block8x8F blueBlock = ref this.B;
CopyToBlock(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock);
}
private static void CopyToBlock(Span<Rgb24> rgbSpan, ref Block8x8F redBlock, ref Block8x8F greenBlock, ref Block8x8F blueBlock)
{
ref Rgb24 rgbStart = ref MemoryMarshal.GetReference(rgbSpan);
for (int i = 0; i < Block8x8F.Size; i++)
{
Rgb24 c = Unsafe.Add(ref rgbStart, (nint)(uint)i);
redBlock[i] = c.R;
greenBlock[i] = c.G;
blueBlock[i] = c.B;
}
}
}
}

148
src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs

@ -92,48 +92,144 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
return tables; return tables;
} }
/// <summary>
/// Optimized method to allocates the correct y, cb, and cr values to the DCT blocks from the given r, g, b values.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ConvertPixelInto( private float CalculateY(byte r, byte g, byte b)
int r,
int g,
int b,
ref Block8x8F yResult,
ref Block8x8F cbResult,
ref Block8x8F crResult,
int i)
{ {
// float y = (0.299F * r) + (0.587F * g) + (0.114F * b); // float y = (0.299F * r) + (0.587F * g) + (0.114F * b);
yResult[i] = (this.YRTable[r] + this.YGTable[g] + this.YBTable[b]) >> ScaleBits; return (this.YRTable[r] + this.YGTable[g] + this.YBTable[b]) >> ScaleBits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private float CalculateCb(byte r, byte g, byte b)
{
// float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); // float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b));
cbResult[i] = (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits; return (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private float CalculateCr(byte r, byte g, byte b)
{
// float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); // float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b));
crResult[i] = (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits; return (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits;
} }
public void Convert(Span<Rgb24> rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock) /// <summary>
/// Converts Rgb24 pixels into YCbCr color space with 4:4:4 subsampling sampling of luminance and chroma.
/// </summary>
/// <param name="rgbSpan">Span of Rgb24 pixel data</param>
/// <param name="yBlock">Resulting Y values block</param>
/// <param name="cbBlock">Resulting Cb values block</param>
/// <param name="crBlock">Resulting Cr values block</param>
public void Convert444(Span<Rgb24> rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock)
{ {
ref Rgb24 rgbStart = ref rgbSpan[0]; ref Rgb24 rgbStart = ref rgbSpan[0];
for (int i = 0; i < 64; i++) for (int i = 0; i < Block8x8F.Size; i++)
{ {
ref Rgb24 c = ref Unsafe.Add(ref rgbStart, i); Rgb24 c = Unsafe.Add(ref rgbStart, i);
this.ConvertPixelInto( yBlock[i] = this.CalculateY(c.R, c.G, c.B);
c.R, cbBlock[i] = this.CalculateCb(c.R, c.G, c.B);
c.G, crBlock[i] = this.CalculateCr(c.R, c.G, c.B);
c.B,
ref yBlock,
ref cbBlock,
ref crBlock,
i);
} }
} }
/// <summary>
/// Converts Rgb24 pixels into YCbCr color space with 4:2:0 subsampling of luminance and chroma.
/// </summary>
/// <remarks>Calculates 2 out of 4 luminance blocks and half of chroma blocks. This method must be called twice per 4x 8x8 DCT blocks with different row param.</remarks>
/// <param name="rgbSpan">Span of Rgb24 pixel data</param>
/// <param name="yBlockLeft">First or "left" resulting Y block</param>
/// <param name="yBlockRight">Second or "right" resulting Y block</param>
/// <param name="cbBlock">Resulting Cb values block</param>
/// <param name="crBlock">Resulting Cr values block</param>
/// <param name="row">Row index of the 16x16 block, 0 or 1</param>
public void Convert420(Span<Rgb24> rgbSpan, ref Block8x8F yBlockLeft, ref Block8x8F yBlockRight, ref Block8x8F cbBlock, ref Block8x8F crBlock, int row)
{
DebugGuard.MustBeBetweenOrEqualTo(row, 0, 1, nameof(row));
ref float yBlockLeftRef = ref Unsafe.As<Block8x8F, float>(ref yBlockLeft);
ref float yBlockRightRef = ref Unsafe.As<Block8x8F, float>(ref yBlockRight);
// 0-31 or 32-63
// upper or lower part
int chromaWriteOffset = row * (Block8x8F.Size / 2);
ref float cbBlockRef = ref Unsafe.Add(ref Unsafe.As<Block8x8F, float>(ref cbBlock), chromaWriteOffset);
ref float crBlockRef = ref Unsafe.Add(ref Unsafe.As<Block8x8F, float>(ref crBlock), chromaWriteOffset);
ref Rgb24 rgbStart = ref rgbSpan[0];
for (int i = 0; i < 8; i += 2)
{
int yBlockWriteOffset = i * 8;
ref Rgb24 stride = ref Unsafe.Add(ref rgbStart, i * 16);
int chromaOffset = 8 * (i / 2);
// left
this.ConvertChunk420(
ref stride,
ref Unsafe.Add(ref yBlockLeftRef, yBlockWriteOffset),
ref Unsafe.Add(ref cbBlockRef, chromaOffset),
ref Unsafe.Add(ref crBlockRef, chromaOffset));
// right
this.ConvertChunk420(
ref Unsafe.Add(ref stride, 8),
ref Unsafe.Add(ref yBlockRightRef, yBlockWriteOffset),
ref Unsafe.Add(ref cbBlockRef, chromaOffset + 4),
ref Unsafe.Add(ref crBlockRef, chromaOffset + 4));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ConvertChunk420(ref Rgb24 stride, ref float yBlock, ref float cbBlock, ref float crBlock)
{
// jpeg 8x8 blocks are processed as 16x16 blocks with 16x8 subpasses (this is done for performance reasons)
// each row is 16 pixels wide thus +16 stride reference offset
// resulting luminance (Y`) are sampled at original resolution thus +8 reference offset
for (int k = 0; k < 8; k += 2)
{
ref float yBlockRef = ref Unsafe.Add(ref yBlock, k);
// top row
Rgb24 px0 = Unsafe.Add(ref stride, k);
Rgb24 px1 = Unsafe.Add(ref stride, k + 1);
yBlockRef = this.CalculateY(px0.R, px0.G, px0.B);
Unsafe.Add(ref yBlockRef, 1) = this.CalculateY(px1.R, px1.G, px1.B);
// bottom row
Rgb24 px2 = Unsafe.Add(ref stride, k + 16);
Rgb24 px3 = Unsafe.Add(ref stride, k + 17);
Unsafe.Add(ref yBlockRef, 8) = this.CalculateY(px2.R, px2.G, px2.B);
Unsafe.Add(ref yBlockRef, 9) = this.CalculateY(px3.R, px3.G, px3.B);
// chroma average for 2x2 pixel block
Unsafe.Add(ref cbBlock, k / 2) = this.CalculateAverageCb(px0, px1, px2, px3);
Unsafe.Add(ref crBlock, k / 2) = this.CalculateAverageCr(px0, px1, px2, px3);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private float CalculateAverageCb(Rgb24 px0, Rgb24 px1, Rgb24 px2, Rgb24 px3)
{
return 0.25f
* (this.CalculateCb(px0.R, px0.G, px0.B)
+ this.CalculateCb(px1.R, px1.G, px1.B)
+ this.CalculateCb(px2.R, px2.G, px2.B)
+ this.CalculateCb(px3.R, px3.G, px3.B));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private float CalculateAverageCr(Rgb24 px0, Rgb24 px1, Rgb24 px2, Rgb24 px3)
{
return 0.25f
* (this.CalculateCr(px0.R, px0.G, px0.B)
+ this.CalculateCr(px1.R, px1.G, px1.B)
+ this.CalculateCr(px2.R, px2.G, px2.B)
+ this.CalculateCr(px3.R, px3.G, px3.B));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Fix(float x) private static int Fix(float x)
=> (int)((x * (1L << ScaleBits)) + 0.5F); => (int)((x * (1L << ScaleBits)) + 0.5F);

195
src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.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.
using System; using System;
@ -27,19 +27,45 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
} }
} }
public static int AvxCompatibilityPadding
{
// rgb byte matrices contain 8 strides by 8 pixels each, thus 64 pixels total
// Strides are stored sequentially - one big span of 64 * 3 = 192 bytes
// Each stride has exactly 3 * 8 = 24 bytes or 3 * 8 * 8 = 192 bits
// Avx registers are 256 bits so rgb span will be loaded with extra 64 bits from the next stride:
// stride 0 0 - 192 -(+64bits)-> 256
// stride 1 192 - 384 -(+64bits)-> 448
// stride 2 384 - 576 -(+64bits)-> 640
// stride 3 576 - 768 -(+64bits)-> 832
// stride 4 768 - 960 -(+64bits)-> 1024
// stride 5 960 - 1152 -(+64bits)-> 1216
// stride 6 1152 - 1344 -(+64bits)-> 1408
// stride 7 1344 - 1536 -(+64bits)-> 1600 <-- READ ACCESS VIOLATION
//
// Total size of the 64 pixel rgb span: 64 * 3 * 8 = 1536 bits, avx operations require 1600 bits
// This is not permitted - we are reading foreign memory
//
// 8 byte padding to rgb byte span will solve this problem without extra code in converters
get
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (IsSupported)
{
return 8;
}
#endif
return 0;
}
}
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
private static ReadOnlySpan<byte> MoveFirst24BytesToSeparateLanes => new byte[] private static ReadOnlySpan<byte> MoveFirst24BytesToSeparateLanes => new byte[]
{ {
0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0,
3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0
}; };
private static ReadOnlySpan<byte> MoveLast24BytesToSeparateLanes => new byte[]
{
2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0,
5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0
};
private static ReadOnlySpan<byte> ExtractRgb => new byte[] private static ReadOnlySpan<byte> ExtractRgb => new byte[]
{ {
0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF,
@ -47,7 +73,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}; };
#endif #endif
public static void Convert(ReadOnlySpan<Rgb24> rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock) /// <summary>
/// Converts 8x8 Rgb24 pixel matrix to YCbCr pixel matrices with 4:4:4 subsampling
/// </summary>
/// <remarks>Total size of rgb span must be 200 bytes</remarks>
/// <param name="rgbSpan">Span of rgb pixels with size of 64</param>
/// <param name="yBlock">8x8 destination matrix of Luminance(Y) converted data</param>
/// <param name="cbBlock">8x8 destination matrix of Chrominance(Cb) converted data</param>
/// <param name="crBlock">8x8 destination matrix of Chrominance(Cr) converted data</param>
public static void Convert444(ReadOnlySpan<Rgb24> rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock)
{ {
Debug.Assert(IsSupported, "AVX2 is required to run this converter"); Debug.Assert(IsSupported, "AVX2 is required to run this converter");
@ -63,18 +97,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
var f05 = Vector256.Create(0.5f); var f05 = Vector256.Create(0.5f);
var zero = Vector256.Create(0).AsByte(); var zero = Vector256.Create(0).AsByte();
ref Vector256<byte> inRef = ref Unsafe.As<Rgb24, Vector256<byte>>(ref MemoryMarshal.GetReference(rgbSpan)); ref Vector256<byte> rgbByteSpan = ref Unsafe.As<Rgb24, Vector256<byte>>(ref MemoryMarshal.GetReference(rgbSpan));
ref Vector256<float> destYRef = ref Unsafe.As<Block8x8F, Vector256<float>>(ref yBlock); ref Vector256<float> destYRef = ref yBlock.V0;
ref Vector256<float> destCbRef = ref Unsafe.As<Block8x8F, Vector256<float>>(ref cbBlock); ref Vector256<float> destCbRef = ref cbBlock.V0;
ref Vector256<float> destCrRef = ref Unsafe.As<Block8x8F, Vector256<float>>(ref crBlock); ref Vector256<float> destCrRef = ref crBlock.V0;
var extractToLanesMask = Unsafe.As<byte, Vector256<uint>>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes)); var extractToLanesMask = Unsafe.As<byte, Vector256<uint>>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes));
var extractRgbMask = Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(ExtractRgb)); var extractRgbMask = Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(ExtractRgb));
Vector256<byte> rgb, rg, bx; Vector256<byte> rgb, rg, bx;
Vector256<float> r, g, b; Vector256<float> r, g, b;
for (int i = 0; i < 7; i++)
const int bytesPerRgbStride = 24;
for (int i = 0; i < 8; i++)
{ {
rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref inRef, (IntPtr)(24 * i)).AsUInt32(), extractToLanesMask).AsByte(); rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * i)).AsUInt32(), extractToLanesMask).AsByte();
rgb = Avx2.Shuffle(rgb, extractRgbMask); rgb = Avx2.Shuffle(rgb, extractRgbMask);
@ -94,27 +130,130 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
// 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)) // 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b))
Unsafe.Add(ref destCrRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); Unsafe.Add(ref destCrRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r));
} }
#endif
}
/// <summary>
/// Converts 16x8 Rgb24 pixels matrix to 2 Y 8x8 matrices with 4:2:0 subsampling
/// </summary>
public static void Convert420(ReadOnlySpan<Rgb24> rgbSpan, ref Block8x8F yBlockLeft, ref Block8x8F yBlockRight, ref Block8x8F cbBlock, ref Block8x8F crBlock, int row)
{
Debug.Assert(IsSupported, "AVX2 is required to run this converter");
#if SUPPORTS_RUNTIME_INTRINSICS
var f0299 = Vector256.Create(0.299f);
var f0587 = Vector256.Create(0.587f);
var f0114 = Vector256.Create(0.114f);
var fn0168736 = Vector256.Create(-0.168736f);
var fn0331264 = Vector256.Create(-0.331264f);
var f128 = Vector256.Create(128f);
var fn0418688 = Vector256.Create(-0.418688f);
var fn0081312F = Vector256.Create(-0.081312F);
var f05 = Vector256.Create(0.5f);
var zero = Vector256.Create(0).AsByte();
ref Vector256<byte> rgbByteSpan = ref Unsafe.As<Rgb24, Vector256<byte>>(ref MemoryMarshal.GetReference(rgbSpan));
int destOffset = row * 4;
extractToLanesMask = Unsafe.As<byte, Vector256<uint>>(ref MemoryMarshal.GetReference(MoveLast24BytesToSeparateLanes)); ref Vector256<float> destCbRef = ref Unsafe.Add(ref Unsafe.As<Block8x8F, Vector256<float>>(ref cbBlock), destOffset);
rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref inRef, (IntPtr)160).AsUInt32(), extractToLanesMask).AsByte(); ref Vector256<float> destCrRef = ref Unsafe.Add(ref Unsafe.As<Block8x8F, Vector256<float>>(ref crBlock), destOffset);
rgb = Avx2.Shuffle(rgb, extractRgbMask);
rg = Avx2.UnpackLow(rgb, zero); var extractToLanesMask = Unsafe.As<byte, Vector256<uint>>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes));
bx = Avx2.UnpackHigh(rgb, zero); var extractRgbMask = Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(ExtractRgb));
Vector256<byte> rgb, rg, bx;
Vector256<float> r, g, b;
Span<Vector256<float>> rDataLanes = stackalloc Vector256<float>[4];
Span<Vector256<float>> gDataLanes = stackalloc Vector256<float>[4];
Span<Vector256<float>> bDataLanes = stackalloc Vector256<float>[4];
const int bytesPerRgbStride = 24;
for (int i = 0; i < 4; i++)
{
// 16x2 => 8x1
// left 8x8 column conversions
for (int j = 0; j < 4; j += 2)
{
rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * ((i * 4) + j))).AsUInt32(), extractToLanesMask).AsByte();
rgb = Avx2.Shuffle(rgb, extractRgbMask);
rg = Avx2.UnpackLow(rgb, zero);
bx = Avx2.UnpackHigh(rgb, zero);
r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32());
g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32());
b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32());
int yBlockVerticalOffset = (i * 2) + ((j & 2) >> 1);
// (0.299F * r) + (0.587F * g) + (0.114F * b);
Unsafe.Add(ref yBlockLeft.V0, yBlockVerticalOffset) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r);
rDataLanes[j] = r;
gDataLanes[j] = g;
bDataLanes[j] = b;
}
// 16x2 => 8x1
// right 8x8 column conversions
for (int j = 1; j < 4; j += 2)
{
rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * ((i * 4) + j))).AsUInt32(), extractToLanesMask).AsByte();
rgb = Avx2.Shuffle(rgb, extractRgbMask);
rg = Avx2.UnpackLow(rgb, zero);
bx = Avx2.UnpackHigh(rgb, zero);
r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32()); r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32());
g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32()); g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32());
b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32()); b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32());
// (0.299F * r) + (0.587F * g) + (0.114F * b); int yBlockVerticalOffset = (i * 2) + ((j & 2) >> 1);
Unsafe.Add(ref destYRef, 7) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r);
// 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)) // (0.299F * r) + (0.587F * g) + (0.114F * b);
Unsafe.Add(ref destCbRef, 7) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); Unsafe.Add(ref yBlockRight.V0, yBlockVerticalOffset) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r);
// 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)) rDataLanes[j] = r;
Unsafe.Add(ref destCrRef, 7) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); gDataLanes[j] = g;
bDataLanes[j] = b;
}
r = Scale16x2_8x1(rDataLanes);
g = Scale16x2_8x1(gDataLanes);
b = Scale16x2_8x1(bDataLanes);
// 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b))
Unsafe.Add(ref destCbRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r));
// 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b))
Unsafe.Add(ref destCrRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r));
}
#endif #endif
} }
#if SUPPORTS_RUNTIME_INTRINSICS
/// <summary>
/// Scales 16x2 matrix to 8x1 using 2x2 average
/// </summary>
/// <param name="v">Input matrix consisting of 4 256bit vectors</param>
/// <returns>256bit vector containing upper and lower scaled parts of the input matrix</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Vector256<float> Scale16x2_8x1(ReadOnlySpan<Vector256<float>> v)
{
Debug.Assert(Avx2.IsSupported, "AVX2 is required to run this converter");
DebugGuard.IsTrue(v.Length == 4, "Input span must consist of 4 elements");
var f025 = Vector256.Create(0.25f);
Vector256<float> left = Avx.Add(v[0], v[2]);
Vector256<float> right = Avx.Add(v[1], v[3]);
Vector256<float> avg2x2 = Avx.Multiply(Avx.HorizontalAdd(left, right), f025);
return Avx2.Permute4x64(avg2x2.AsDouble(), 0b11_01_10_00).AsSingle();
}
#endif
} }
} }

121
src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs

@ -0,0 +1,121 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.InteropServices;
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 -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks.
/// </summary>
/// <typeparam name="TPixel">The pixel type to work on</typeparam>
internal ref struct YCbCrForwardConverter420<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Number of pixels processed per single <see cref="Convert(int, int, ref RowOctet{TPixel}, int)"/> call
/// </summary>
private const int PixelsPerSample = 16 * 8;
/// <summary>
/// Total byte size of processed pixels converted from TPixel to <see cref="Rgb24"/>
/// </summary>
private const int RgbSpanByteSize = PixelsPerSample * 3;
/// <summary>
/// <see cref="Size"/> of sampling area from given frame pixel buffer
/// </summary>
private static readonly Size SampleSize = new Size(16, 8);
/// <summary>
/// The left Y component
/// </summary>
public Block8x8F YLeft;
/// <summary>
/// The left Y component
/// </summary>
public Block8x8F YRight;
/// <summary>
/// The Cb component
/// </summary>
public Block8x8F Cb;
/// <summary>
/// The Cr component
/// </summary>
public Block8x8F Cr;
/// <summary>
/// The color conversion tables
/// </summary>
private RgbToYCbCrConverterLut colorTables;
/// <summary>
/// Temporal 16x8 block to hold TPixel data
/// </summary>
private readonly Span<TPixel> pixelSpan;
/// <summary>
/// Temporal RGB block
/// </summary>
private readonly Span<Rgb24> rgbSpan;
/// <summary>
/// Sampled pixel buffer size
/// </summary>
private readonly Size samplingAreaSize;
/// <summary>
/// <see cref="Configuration"/> for internal operations
/// </summary>
private readonly Configuration config;
public YCbCrForwardConverter420(ImageFrame<TPixel> frame)
{
// matrices would be filled during convert calls
this.YLeft = default;
this.YRight = default;
this.Cb = default;
this.Cr = default;
// temporal pixel buffers
this.pixelSpan = new TPixel[PixelsPerSample].AsSpan();
this.rgbSpan = MemoryMarshal.Cast<byte, Rgb24>(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan());
// frame data
this.samplingAreaSize = new Size(frame.Width, frame.Height);
this.config = frame.GetConfiguration();
// conversion vector fallback data
if (!RgbToYCbCrConverterVectorized.IsSupported)
{
this.colorTables = RgbToYCbCrConverterLut.Create();
}
else
{
this.colorTables = default;
}
}
public void Convert(int x, int y, ref RowOctet<TPixel> currentRows, int idx)
{
YCbCrForwardConverter<TPixel>.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize);
PixelOperations<TPixel>.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan);
if (RgbToYCbCrConverterVectorized.IsSupported)
{
RgbToYCbCrConverterVectorized.Convert420(this.rgbSpan, ref this.YLeft, ref this.YRight, ref this.Cb, ref this.Cr, idx);
}
else
{
this.colorTables.Convert420(this.rgbSpan, ref this.YLeft, ref this.YRight, ref this.Cb, ref this.Cr, idx);
}
}
}
}

122
src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs

@ -0,0 +1,122 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.InteropServices;
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 -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks.
/// </summary>
/// <typeparam name="TPixel">The pixel type to work on</typeparam>
internal ref struct YCbCrForwardConverter444<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Number of pixels processed per single <see cref="Convert(int, int, ref RowOctet{TPixel})"/> call
/// </summary>
private const int PixelsPerSample = 8 * 8;
/// <summary>
/// Total byte size of processed pixels converted from TPixel to <see cref="Rgb24"/>
/// </summary>
private const int RgbSpanByteSize = PixelsPerSample * 3;
/// <summary>
/// <see cref="Size"/> of sampling area from given frame pixel buffer
/// </summary>
private static readonly Size SampleSize = new Size(8, 8);
/// <summary>
/// The Y component
/// </summary>
public Block8x8F Y;
/// <summary>
/// The Cb component
/// </summary>
public Block8x8F Cb;
/// <summary>
/// The Cr component
/// </summary>
public Block8x8F Cr;
/// <summary>
/// The color conversion tables
/// </summary>
private RgbToYCbCrConverterLut colorTables;
/// <summary>
/// Temporal 64-byte span to hold unconverted TPixel data
/// </summary>
private readonly Span<TPixel> pixelSpan;
/// <summary>
/// Temporal 64-byte span to hold converted Rgb24 data
/// </summary>
private readonly Span<Rgb24> rgbSpan;
/// <summary>
/// Sampled pixel buffer size
/// </summary>
private readonly Size samplingAreaSize;
/// <summary>
/// <see cref="Configuration"/> for internal operations
/// </summary>
private readonly Configuration config;
public YCbCrForwardConverter444(ImageFrame<TPixel> frame)
{
// matrices would be filled during convert calls
this.Y = default;
this.Cb = default;
this.Cr = default;
// temporal pixel buffers
this.pixelSpan = new TPixel[PixelsPerSample].AsSpan();
this.rgbSpan = MemoryMarshal.Cast<byte, Rgb24>(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan());
// frame data
this.samplingAreaSize = new Size(frame.Width, frame.Height);
this.config = frame.GetConfiguration();
// conversion vector fallback data
if (!RgbToYCbCrConverterVectorized.IsSupported)
{
this.colorTables = RgbToYCbCrConverterLut.Create();
}
else
{
this.colorTables = default;
}
}
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (<see cref="Y"/>, <see cref="Cb"/>, <see cref="Cr"/>)
/// </summary>
public void Convert(int x, int y, ref RowOctet<TPixel> currentRows)
{
YCbCrForwardConverter<TPixel>.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize);
PixelOperations<TPixel>.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan);
ref Block8x8F yBlock = ref this.Y;
ref Block8x8F cbBlock = ref this.Cb;
ref Block8x8F crBlock = ref this.Cr;
if (RgbToYCbCrConverterVectorized.IsSupported)
{
RgbToYCbCrConverterVectorized.Convert444(this.rgbSpan, ref yBlock, ref cbBlock, ref crBlock);
}
else
{
this.colorTables.Convert444(this.rgbSpan, ref yBlock, ref cbBlock, ref crBlock);
}
}
}
}

90
src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs

@ -2,81 +2,59 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using SixLabors.ImageSharp.Advanced; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{ {
/// <summary> internal static class YCbCrForwardConverter<TPixel>
/// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks.
/// </summary>
/// <typeparam name="TPixel">The pixel type to work on</typeparam>
internal ref struct YCbCrForwardConverter<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
/// <summary> public static void LoadAndStretchEdges(RowOctet<TPixel> source, Span<TPixel> dest, Point start, Size sampleSize, Size totalSize)
/// The Y component {
/// </summary> DebugGuard.MustBeBetweenOrEqualTo(start.X, 0, totalSize.Width - 1, nameof(start.X));
public Block8x8F Y; DebugGuard.MustBeBetweenOrEqualTo(start.Y, 0, totalSize.Height - 1, nameof(start.Y));
/// <summary>
/// The Cb component
/// </summary>
public Block8x8F Cb;
/// <summary>
/// The Cr component
/// </summary>
public Block8x8F Cr;
/// <summary> int width = Math.Min(sampleSize.Width, totalSize.Width - start.X);
/// The color conversion tables int height = Math.Min(sampleSize.Height, totalSize.Height - start.Y);
/// </summary>
private RgbToYCbCrConverterLut colorTables;
/// <summary> uint byteWidth = (uint)(width * Unsafe.SizeOf<TPixel>());
/// Temporal 8x8 block to hold TPixel data int remainderXCount = sampleSize.Width - width;
/// </summary>
private GenericBlock8x8<TPixel> pixelBlock;
/// <summary> ref byte blockStart = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<TPixel, byte>(dest));
/// Temporal RGB block int rowSizeInBytes = sampleSize.Width * Unsafe.SizeOf<TPixel>();
/// </summary>
private GenericBlock8x8<Rgb24> rgbBlock;
public static YCbCrForwardConverter<TPixel> Create() for (int y = 0; y < height; y++)
{
var result = default(YCbCrForwardConverter<TPixel>);
if (!RgbToYCbCrConverterVectorized.IsSupported)
{ {
// Avoid creating lookup tables, when vectorized converter is supported Span<TPixel> row = source[y];
result.colorTables = RgbToYCbCrConverterLut.Create();
}
return result; ref byte s = ref Unsafe.As<TPixel, byte>(ref row[start.X]);
} ref byte d = ref Unsafe.Add(ref blockStart, y * rowSizeInBytes);
/// <summary> Unsafe.CopyBlock(ref d, ref s, byteWidth);
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (<see cref="Y"/>, <see cref="Cb"/>, <see cref="Cr"/>)
/// </summary> ref TPixel last = ref Unsafe.Add(ref Unsafe.As<byte, TPixel>(ref d), width - 1);
public void Convert(ImageFrame<TPixel> frame, int x, int y, ref RowOctet<TPixel> currentRows)
{
this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, ref currentRows);
Span<Rgb24> rgbSpan = this.rgbBlock.AsSpanUnsafe(); for (int x = 1; x <= remainderXCount; x++)
PixelOperations<TPixel>.Instance.ToRgb24(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), rgbSpan); {
Unsafe.Add(ref last, x) = last;
}
}
ref Block8x8F yBlock = ref this.Y; int remainderYCount = sampleSize.Height - height;
ref Block8x8F cbBlock = ref this.Cb;
ref Block8x8F crBlock = ref this.Cr;
if (RgbToYCbCrConverterVectorized.IsSupported) if (remainderYCount == 0)
{ {
RgbToYCbCrConverterVectorized.Convert(rgbSpan, ref yBlock, ref cbBlock, ref crBlock); return;
} }
else
ref byte lastRowStart = ref Unsafe.Add(ref blockStart, (height - 1) * rowSizeInBytes);
for (int y = 1; y <= remainderYCount; y++)
{ {
this.colorTables.Convert(rgbSpan, ref yBlock, ref cbBlock, ref crBlock); ref byte remStart = ref Unsafe.Add(ref lastRowStart, rowSizeInBytes * y);
Unsafe.CopyBlock(ref remStart, ref lastRowStart, (uint)rowSizeInBytes);
} }
} }
} }

161
src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.Intrinsic.cs

@ -0,0 +1,161 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal static partial class FastFloatingPointDCT
{
#pragma warning disable SA1310, SA1311, IDE1006 // naming rules violation warnings
private static readonly Vector256<float> mm256_F_0_7071 = Vector256.Create(0.707106781f);
private static readonly Vector256<float> mm256_F_0_3826 = Vector256.Create(0.382683433f);
private static readonly Vector256<float> mm256_F_0_5411 = Vector256.Create(0.541196100f);
private static readonly Vector256<float> mm256_F_1_3065 = Vector256.Create(1.306562965f);
private static readonly Vector256<float> mm256_F_1_1758 = Vector256.Create(1.175876f);
private static readonly Vector256<float> mm256_F_n1_9615 = Vector256.Create(-1.961570560f);
private static readonly Vector256<float> mm256_F_n0_3901 = Vector256.Create(-0.390180644f);
private static readonly Vector256<float> mm256_F_n0_8999 = Vector256.Create(-0.899976223f);
private static readonly Vector256<float> mm256_F_n2_5629 = Vector256.Create(-2.562915447f);
private static readonly Vector256<float> mm256_F_0_2986 = Vector256.Create(0.298631336f);
private static readonly Vector256<float> mm256_F_2_0531 = Vector256.Create(2.053119869f);
private static readonly Vector256<float> mm256_F_3_0727 = Vector256.Create(3.072711026f);
private static readonly Vector256<float> mm256_F_1_5013 = Vector256.Create(1.501321110f);
private static readonly Vector256<float> mm256_F_n1_8477 = Vector256.Create(-1.847759065f);
private static readonly Vector256<float> mm256_F_0_7653 = Vector256.Create(0.765366865f);
#pragma warning restore SA1310, SA1311, IDE1006
/// <summary>
/// Apply floating point FDCT inplace using simd operations.
/// </summary>
/// <param name="block">Input matrix.</param>
private static void ForwardTransform_Avx(ref Block8x8F block)
{
DebugGuard.IsTrue(Avx.IsSupported, "Avx support is required to execute this operation.");
// First pass - process rows
block.TransposeInplace();
FDCT8x8_Avx(ref block);
// Second pass - process columns
block.TransposeInplace();
FDCT8x8_Avx(ref block);
}
/// <summary>
/// Apply 1D floating point FDCT inplace using AVX operations on 8x8 matrix.
/// </summary>
/// <remarks>
/// Requires Avx support.
/// </remarks>
/// <param name="block">Input matrix.</param>
public static void FDCT8x8_Avx(ref Block8x8F block)
{
DebugGuard.IsTrue(Avx.IsSupported, "Avx support is required to execute this operation.");
Vector256<float> tmp0 = Avx.Add(block.V0, block.V7);
Vector256<float> tmp7 = Avx.Subtract(block.V0, block.V7);
Vector256<float> tmp1 = Avx.Add(block.V1, block.V6);
Vector256<float> tmp6 = Avx.Subtract(block.V1, block.V6);
Vector256<float> tmp2 = Avx.Add(block.V2, block.V5);
Vector256<float> tmp5 = Avx.Subtract(block.V2, block.V5);
Vector256<float> tmp3 = Avx.Add(block.V3, block.V4);
Vector256<float> tmp4 = Avx.Subtract(block.V3, block.V4);
// Even part
Vector256<float> tmp10 = Avx.Add(tmp0, tmp3);
Vector256<float> tmp13 = Avx.Subtract(tmp0, tmp3);
Vector256<float> tmp11 = Avx.Add(tmp1, tmp2);
Vector256<float> tmp12 = Avx.Subtract(tmp1, tmp2);
block.V0 = Avx.Add(tmp10, tmp11);
block.V4 = Avx.Subtract(tmp10, tmp11);
Vector256<float> z1 = Avx.Multiply(Avx.Add(tmp12, tmp13), mm256_F_0_7071);
block.V2 = Avx.Add(tmp13, z1);
block.V6 = Avx.Subtract(tmp13, z1);
// Odd part
tmp10 = Avx.Add(tmp4, tmp5);
tmp11 = Avx.Add(tmp5, tmp6);
tmp12 = Avx.Add(tmp6, tmp7);
Vector256<float> z5 = Avx.Multiply(Avx.Subtract(tmp10, tmp12), mm256_F_0_3826);
Vector256<float> z2 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, mm256_F_0_5411, tmp10);
Vector256<float> z4 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, mm256_F_1_3065, tmp12);
Vector256<float> z3 = Avx.Multiply(tmp11, mm256_F_0_7071);
Vector256<float> z11 = Avx.Add(tmp7, z3);
Vector256<float> z13 = Avx.Subtract(tmp7, z3);
block.V5 = Avx.Add(z13, z2);
block.V3 = Avx.Subtract(z13, z2);
block.V1 = Avx.Add(z11, z4);
block.V7 = Avx.Subtract(z11, z4);
}
/// <summary>
/// Combined operation of <see cref="IDCT8x4_LeftPart(ref Block8x8F, ref Block8x8F)"/> and <see cref="IDCT8x4_RightPart(ref Block8x8F, ref Block8x8F)"/>
/// using AVX commands.
/// </summary>
/// <param name="s">Source</param>
/// <param name="d">Destination</param>
public static void IDCT8x8_Avx(ref Block8x8F s, ref Block8x8F d)
{
Debug.Assert(Avx.IsSupported, "AVX is required to execute this method");
Vector256<float> my1 = s.V1;
Vector256<float> my7 = s.V7;
Vector256<float> mz0 = Avx.Add(my1, my7);
Vector256<float> my3 = s.V3;
Vector256<float> mz2 = Avx.Add(my3, my7);
Vector256<float> my5 = s.V5;
Vector256<float> mz1 = Avx.Add(my3, my5);
Vector256<float> mz3 = Avx.Add(my1, my5);
Vector256<float> mz4 = Avx.Multiply(Avx.Add(mz0, mz1), mm256_F_1_1758);
mz2 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, mz2, mm256_F_n1_9615);
mz3 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, mz3, mm256_F_n0_3901);
mz0 = Avx.Multiply(mz0, mm256_F_n0_8999);
mz1 = Avx.Multiply(mz1, mm256_F_n2_5629);
Vector256<float> mb3 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz0, my7, mm256_F_0_2986), mz2);
Vector256<float> mb2 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz1, my5, mm256_F_2_0531), mz3);
Vector256<float> mb1 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz1, my3, mm256_F_3_0727), mz2);
Vector256<float> mb0 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz0, my1, mm256_F_1_5013), mz3);
Vector256<float> my2 = s.V2;
Vector256<float> my6 = s.V6;
mz4 = Avx.Multiply(Avx.Add(my2, my6), mm256_F_0_5411);
Vector256<float> my0 = s.V0;
Vector256<float> my4 = s.V4;
mz0 = Avx.Add(my0, my4);
mz1 = Avx.Subtract(my0, my4);
mz2 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, my6, mm256_F_n1_8477);
mz3 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, my2, mm256_F_0_7653);
my0 = Avx.Add(mz0, mz3);
my3 = Avx.Subtract(mz0, mz3);
my1 = Avx.Add(mz1, mz2);
my2 = Avx.Subtract(mz1, mz2);
d.V0 = Avx.Add(my0, mb0);
d.V7 = Avx.Subtract(my0, mb0);
d.V1 = Avx.Add(my1, mb1);
d.V6 = Avx.Subtract(my1, mb1);
d.V2 = Avx.Add(my2, mb2);
d.V5 = Avx.Subtract(my2, mb2);
d.V3 = Avx.Add(my3, mb3);
d.V4 = Avx.Subtract(my3, mb3);
}
}
}
#endif

486
src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs

@ -3,6 +3,9 @@
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics.X86;
#endif
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components
@ -10,58 +13,328 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <summary> /// <summary>
/// Contains inaccurate, but fast forward and inverse DCT implementations. /// Contains inaccurate, but fast forward and inverse DCT implementations.
/// </summary> /// </summary>
internal static class FastFloatingPointDCT internal static partial class FastFloatingPointDCT
{ {
#pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore #pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore
private const float C_1_175876 = 1.175875602f; private const float C_1_175876 = 1.175875602f;
private const float C_1_961571 = -1.961570560f; private const float C_1_961571 = -1.961570560f;
private const float C_0_390181 = -0.390180644f; private const float C_0_390181 = -0.390180644f;
private const float C_0_899976 = -0.899976223f; private const float C_0_899976 = -0.899976223f;
private const float C_2_562915 = -2.562915447f; private const float C_2_562915 = -2.562915447f;
private const float C_0_298631 = 0.298631336f; private const float C_0_298631 = 0.298631336f;
private const float C_2_053120 = 2.053119869f; private const float C_2_053120 = 2.053119869f;
private const float C_3_072711 = 3.072711026f; private const float C_3_072711 = 3.072711026f;
private const float C_1_501321 = 1.501321110f; private const float C_1_501321 = 1.501321110f;
private const float C_0_541196 = 0.541196100f; private const float C_0_541196 = 0.541196100f;
private const float C_1_847759 = -1.847759065f; private const float C_1_847759 = -1.847759065f;
private const float C_0_765367 = 0.765366865f; private const float C_0_765367 = 0.765366865f;
private const float C_0_125 = 0.1250f; private const float C_0_125 = 0.1250f;
#pragma warning disable SA1311, IDE1006 // naming rules violation warnings
private static readonly Vector4 mm128_F_0_7071 = new Vector4(0.707106781f);
private static readonly Vector4 mm128_F_0_3826 = new Vector4(0.382683433f);
private static readonly Vector4 mm128_F_0_5411 = new Vector4(0.541196100f);
private static readonly Vector4 mm128_F_1_3065 = new Vector4(1.306562965f);
#pragma warning restore SA1311, IDE1006
#pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore #pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore
private static readonly Vector4 InvSqrt2 = new Vector4(0.707107f);
/// <summary> /// <summary>
/// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization). /// Gets reciprocal coefficients for jpeg quantization tables calculation.
/// Ported from https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239 /// </summary>
/// <remarks>
/// <para>
/// Current FDCT implementation expects its results to be multiplied by
/// a reciprocal quantization table. To get 8x8 reciprocal block values in this
/// table must be divided by quantization table values scaled with quality settings.
/// </para>
/// <para>
/// These values were calculates with this formula:
/// <code>
/// value[row * 8 + col] = scalefactor[row] * scalefactor[col] * 8;
/// </code>
/// Where:
/// <code>
/// scalefactor[0] = 1
/// </code>
/// <code>
/// scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7
/// </code>
/// Values are also scaled by 8 so DCT code won't do extra division/multiplication.
/// </para>
/// </remarks>
internal static readonly float[] DctReciprocalAdjustmentCoefficients = new float[]
{
0.125f, 0.09011998f, 0.09567086f, 0.10630376f, 0.125f, 0.15909483f, 0.23096988f, 0.45306373f,
0.09011998f, 0.064972885f, 0.068974845f, 0.07664074f, 0.09011998f, 0.11470097f, 0.16652f, 0.32664075f,
0.09567086f, 0.068974845f, 0.07322331f, 0.081361376f, 0.09567086f, 0.121765904f, 0.17677669f, 0.34675997f,
0.10630376f, 0.07664074f, 0.081361376f, 0.09040392f, 0.10630376f, 0.13529903f, 0.19642374f, 0.38529903f,
0.125f, 0.09011998f, 0.09567086f, 0.10630376f, 0.125f, 0.15909483f, 0.23096988f, 0.45306373f,
0.15909483f, 0.11470097f, 0.121765904f, 0.13529903f, 0.15909483f, 0.2024893f, 0.2939689f, 0.5766407f,
0.23096988f, 0.16652f, 0.17677669f, 0.19642374f, 0.23096988f, 0.2939689f, 0.4267767f, 0.8371526f,
0.45306373f, 0.32664075f, 0.34675997f, 0.38529903f, 0.45306373f, 0.5766407f, 0.8371526f, 1.642134f,
};
/// <summary>
/// Adjusts given quantization table to be complient with FDCT implementation.
/// </summary>
/// <remarks>
/// See <see cref="DctReciprocalAdjustmentCoefficients"/> docs for explanation.
/// </remarks>
/// <param name="quantizationtable">Quantization table to adjust.</param>
public static void AdjustToFDCT(ref Block8x8F quantizationtable)
{
for (int i = 0; i < Block8x8F.Size; i++)
{
quantizationtable[i] = DctReciprocalAdjustmentCoefficients[i] / quantizationtable[i];
}
}
/// <summary>
/// Apply 2D floating point FDCT inplace.
/// </summary>
/// <param name="block">Input matrix.</param>
public static void TransformFDCT(ref Block8x8F block)
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx.IsSupported)
{
ForwardTransform_Avx(ref block);
}
else
#endif
if (Vector.IsHardwareAccelerated)
{
ForwardTransform_Vector4(ref block);
}
else
{
ForwardTransform_Scalar(ref block);
}
}
/// <summary>
/// Apply 2D floating point FDCT inplace using scalar operations.
/// </summary>
/// <remarks>
/// Ported from libjpeg-turbo https://github.com/libjpeg-turbo/libjpeg-turbo/blob/main/jfdctflt.c.
/// </remarks>
/// <param name="block">Input matrix.</param>
private static void ForwardTransform_Scalar(ref Block8x8F block)
{
const int dctSize = 8;
float tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
float tmp10, tmp11, tmp12, tmp13;
float z1, z2, z3, z4, z5, z11, z13;
// First pass - process rows
ref float dataRef = ref Unsafe.As<Block8x8F, float>(ref block);
for (int ctr = 7; ctr >= 0; ctr--)
{
tmp0 = Unsafe.Add(ref dataRef, 0) + Unsafe.Add(ref dataRef, 7);
tmp7 = Unsafe.Add(ref dataRef, 0) - Unsafe.Add(ref dataRef, 7);
tmp1 = Unsafe.Add(ref dataRef, 1) + Unsafe.Add(ref dataRef, 6);
tmp6 = Unsafe.Add(ref dataRef, 1) - Unsafe.Add(ref dataRef, 6);
tmp2 = Unsafe.Add(ref dataRef, 2) + Unsafe.Add(ref dataRef, 5);
tmp5 = Unsafe.Add(ref dataRef, 2) - Unsafe.Add(ref dataRef, 5);
tmp3 = Unsafe.Add(ref dataRef, 3) + Unsafe.Add(ref dataRef, 4);
tmp4 = Unsafe.Add(ref dataRef, 3) - Unsafe.Add(ref dataRef, 4);
// Even part
tmp10 = tmp0 + tmp3;
tmp13 = tmp0 - tmp3;
tmp11 = tmp1 + tmp2;
tmp12 = tmp1 - tmp2;
Unsafe.Add(ref dataRef, 0) = tmp10 + tmp11;
Unsafe.Add(ref dataRef, 4) = tmp10 - tmp11;
z1 = (tmp12 + tmp13) * 0.707106781f;
Unsafe.Add(ref dataRef, 2) = tmp13 + z1;
Unsafe.Add(ref dataRef, 6) = tmp13 - z1;
// Odd part
tmp10 = tmp4 + tmp5;
tmp11 = tmp5 + tmp6;
tmp12 = tmp6 + tmp7;
z5 = (tmp10 - tmp12) * 0.382683433f;
z2 = (0.541196100f * tmp10) + z5;
z4 = (1.306562965f * tmp12) + z5;
z3 = tmp11 * 0.707106781f;
z11 = tmp7 + z3;
z13 = tmp7 - z3;
Unsafe.Add(ref dataRef, 5) = z13 + z2;
Unsafe.Add(ref dataRef, 3) = z13 - z2;
Unsafe.Add(ref dataRef, 1) = z11 + z4;
Unsafe.Add(ref dataRef, 7) = z11 - z4;
dataRef = ref Unsafe.Add(ref dataRef, dctSize);
}
// Second pass - process columns
dataRef = ref Unsafe.As<Block8x8F, float>(ref block);
for (int ctr = 7; ctr >= 0; ctr--)
{
tmp0 = Unsafe.Add(ref dataRef, dctSize * 0) + Unsafe.Add(ref dataRef, dctSize * 7);
tmp7 = Unsafe.Add(ref dataRef, dctSize * 0) - Unsafe.Add(ref dataRef, dctSize * 7);
tmp1 = Unsafe.Add(ref dataRef, dctSize * 1) + Unsafe.Add(ref dataRef, dctSize * 6);
tmp6 = Unsafe.Add(ref dataRef, dctSize * 1) - Unsafe.Add(ref dataRef, dctSize * 6);
tmp2 = Unsafe.Add(ref dataRef, dctSize * 2) + Unsafe.Add(ref dataRef, dctSize * 5);
tmp5 = Unsafe.Add(ref dataRef, dctSize * 2) - Unsafe.Add(ref dataRef, dctSize * 5);
tmp3 = Unsafe.Add(ref dataRef, dctSize * 3) + Unsafe.Add(ref dataRef, dctSize * 4);
tmp4 = Unsafe.Add(ref dataRef, dctSize * 3) - Unsafe.Add(ref dataRef, dctSize * 4);
// Even part
tmp10 = tmp0 + tmp3;
tmp13 = tmp0 - tmp3;
tmp11 = tmp1 + tmp2;
tmp12 = tmp1 - tmp2;
Unsafe.Add(ref dataRef, dctSize * 0) = tmp10 + tmp11;
Unsafe.Add(ref dataRef, dctSize * 4) = tmp10 - tmp11;
z1 = (tmp12 + tmp13) * 0.707106781f;
Unsafe.Add(ref dataRef, dctSize * 2) = tmp13 + z1;
Unsafe.Add(ref dataRef, dctSize * 6) = tmp13 - z1;
// Odd part
tmp10 = tmp4 + tmp5;
tmp11 = tmp5 + tmp6;
tmp12 = tmp6 + tmp7;
z5 = (tmp10 - tmp12) * 0.382683433f;
z2 = (0.541196100f * tmp10) + z5;
z4 = (1.306562965f * tmp12) + z5;
z3 = tmp11 * 0.707106781f;
z11 = tmp7 + z3;
z13 = tmp7 - z3;
Unsafe.Add(ref dataRef, dctSize * 5) = z13 + z2;
Unsafe.Add(ref dataRef, dctSize * 3) = z13 - z2;
Unsafe.Add(ref dataRef, dctSize * 1) = z11 + z4;
Unsafe.Add(ref dataRef, dctSize * 7) = z11 - z4;
dataRef = ref Unsafe.Add(ref dataRef, 1);
}
}
/// <summary>
/// Apply floating point FDCT inplace using <see cref="Vector4"/> API.
/// </summary> /// </summary>
/// <param name="src">Source</param> /// <remarks>
/// <param name="dest">Destination</param> /// This implementation must be called only if hardware supports 4
/// <param name="temp">Temporary block provided by the caller</param> /// floating point numbers vector. Otherwise explicit scalar
public static void TransformIDCT(ref Block8x8F src, ref Block8x8F dest, ref Block8x8F temp) /// implementation <see cref="ForwardTransform_Scalar"/> is faster
/// because it does not rely on matrix transposition.
/// </remarks>
/// <param name="block">Input matrix.</param>
private static void ForwardTransform_Vector4(ref Block8x8F block)
{ {
src.TransposeInto(ref temp); DebugGuard.IsTrue(Vector.IsHardwareAccelerated, "Scalar implementation should be called for non-accelerated hardware.");
// First pass - process rows
block.TransposeInplace();
FDCT8x4_Vector4(ref block.V0L);
FDCT8x4_Vector4(ref block.V0R);
IDCT8x4_LeftPart(ref temp, ref dest); // Second pass - process columns
IDCT8x4_RightPart(ref temp, ref dest); block.TransposeInplace();
FDCT8x4_Vector4(ref block.V0L);
FDCT8x4_Vector4(ref block.V0R);
}
dest.TransposeInto(ref temp); /// <summary>
/// Apply 1D floating point FDCT inplace on 8x4 part of 8x8 matrix.
/// </summary>
/// <remarks>
/// Implemented using Vector4 API operations for either scalar or sse hardware implementation.
/// Must be called on both 8x4 matrix parts for the full FDCT transform.
/// </remarks>
/// <param name="blockRef">Input reference to the first </param>
private static void FDCT8x4_Vector4(ref Vector4 blockRef)
{
Vector4 tmp0 = Unsafe.Add(ref blockRef, 0) + Unsafe.Add(ref blockRef, 14);
Vector4 tmp7 = Unsafe.Add(ref blockRef, 0) - Unsafe.Add(ref blockRef, 14);
Vector4 tmp1 = Unsafe.Add(ref blockRef, 2) + Unsafe.Add(ref blockRef, 12);
Vector4 tmp6 = Unsafe.Add(ref blockRef, 2) - Unsafe.Add(ref blockRef, 12);
Vector4 tmp2 = Unsafe.Add(ref blockRef, 4) + Unsafe.Add(ref blockRef, 10);
Vector4 tmp5 = Unsafe.Add(ref blockRef, 4) - Unsafe.Add(ref blockRef, 10);
Vector4 tmp3 = Unsafe.Add(ref blockRef, 6) + Unsafe.Add(ref blockRef, 8);
Vector4 tmp4 = Unsafe.Add(ref blockRef, 6) - Unsafe.Add(ref blockRef, 8);
// Even part
Vector4 tmp10 = tmp0 + tmp3;
Vector4 tmp13 = tmp0 - tmp3;
Vector4 tmp11 = tmp1 + tmp2;
Vector4 tmp12 = tmp1 - tmp2;
Unsafe.Add(ref blockRef, 0) = tmp10 + tmp11;
Unsafe.Add(ref blockRef, 8) = tmp10 - tmp11;
Vector4 z1 = (tmp12 + tmp13) * mm128_F_0_7071;
Unsafe.Add(ref blockRef, 4) = tmp13 + z1;
Unsafe.Add(ref blockRef, 12) = tmp13 - z1;
// Odd part
tmp10 = tmp4 + tmp5;
tmp11 = tmp5 + tmp6;
tmp12 = tmp6 + tmp7;
Vector4 z5 = (tmp10 - tmp12) * mm128_F_0_3826;
Vector4 z2 = (mm128_F_0_5411 * tmp10) + z5;
Vector4 z4 = (mm128_F_1_3065 * tmp12) + z5;
Vector4 z3 = tmp11 * mm128_F_0_7071;
Vector4 z11 = tmp7 + z3;
Vector4 z13 = tmp7 - z3;
Unsafe.Add(ref blockRef, 10) = z13 + z2;
Unsafe.Add(ref blockRef, 6) = z13 - z2;
Unsafe.Add(ref blockRef, 2) = z11 + z4;
Unsafe.Add(ref blockRef, 14) = z11 - z4;
}
IDCT8x4_LeftPart(ref temp, ref dest); /// <summary>
IDCT8x4_RightPart(ref temp, ref dest); /// Apply floating point IDCT inplace.
/// Ported from https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239.
/// </summary>
/// <param name="block">Input matrix.</param>
/// <param name="temp">Matrix to store temporal results.</param>
public static void TransformIDCT(ref Block8x8F block, ref Block8x8F temp)
{
block.TransposeInplace();
IDCT8x8(ref block, ref temp);
temp.TransposeInplace();
IDCT8x8(ref temp, ref block);
// TODO: What if we leave the blocks in a scaled-by-x8 state until final color packing? // TODO: This can be fused into quantization table step
dest.MultiplyInPlace(C_0_125); block.MultiplyInPlace(C_0_125);
}
/// <summary>
/// Performs 8x8 matrix Inverse Discrete Cosine Transform
/// </summary>
/// <param name="s">Source</param>
/// <param name="d">Destination</param>
private static void IDCT8x8(ref Block8x8F s, ref Block8x8F d)
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx.IsSupported)
{
IDCT8x8_Avx(ref s, ref d);
}
else
#endif
{
IDCT8x4_LeftPart(ref s, ref d);
IDCT8x4_RightPart(ref s, ref d);
}
} }
/// <summary> /// <summary>
@ -178,164 +451,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
d.V3R = my3 + mb3; d.V3R = my3 + mb3;
d.V4R = my3 - mb3; d.V4R = my3 - mb3;
} }
/// <summary>
/// Original:
/// <see>
/// <cref>https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15</cref>
/// </see>
/// </summary>
/// <param name="s">Source</param>
/// <param name="d">Destination</param>
public static void FDCT8x4_LeftPart(ref Block8x8F s, ref Block8x8F d)
{
Vector4 c0 = s.V0L;
Vector4 c1 = s.V7L;
Vector4 t0 = c0 + c1;
Vector4 t7 = c0 - c1;
c1 = s.V6L;
c0 = s.V1L;
Vector4 t1 = c0 + c1;
Vector4 t6 = c0 - c1;
c1 = s.V5L;
c0 = s.V2L;
Vector4 t2 = c0 + c1;
Vector4 t5 = c0 - c1;
c0 = s.V3L;
c1 = s.V4L;
Vector4 t3 = c0 + c1;
Vector4 t4 = c0 - c1;
c0 = t0 + t3;
Vector4 c3 = t0 - t3;
c1 = t1 + t2;
Vector4 c2 = t1 - t2;
d.V0L = c0 + c1;
d.V4L = c0 - c1;
float w0 = 0.541196f;
float w1 = 1.306563f;
d.V2L = (w0 * c2) + (w1 * c3);
d.V6L = (w0 * c3) - (w1 * c2);
w0 = 1.175876f;
w1 = 0.785695f;
c3 = (w0 * t4) + (w1 * t7);
c0 = (w0 * t7) - (w1 * t4);
w0 = 1.387040f;
w1 = 0.275899f;
c2 = (w0 * t5) + (w1 * t6);
c1 = (w0 * t6) - (w1 * t5);
d.V3L = c0 - c2;
d.V5L = c3 - c1;
float invsqrt2 = 0.707107f;
c0 = (c0 + c2) * invsqrt2;
c3 = (c3 + c1) * invsqrt2;
d.V1L = c0 + c3;
d.V7L = c0 - c3;
}
/// <summary>
/// Original:
/// <see>
/// <cref>https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15</cref>
/// </see>
/// </summary>
/// <param name="s">Source</param>
/// <param name="d">Destination</param>
public static void FDCT8x4_RightPart(ref Block8x8F s, ref Block8x8F d)
{
Vector4 c0 = s.V0R;
Vector4 c1 = s.V7R;
Vector4 t0 = c0 + c1;
Vector4 t7 = c0 - c1;
c1 = s.V6R;
c0 = s.V1R;
Vector4 t1 = c0 + c1;
Vector4 t6 = c0 - c1;
c1 = s.V5R;
c0 = s.V2R;
Vector4 t2 = c0 + c1;
Vector4 t5 = c0 - c1;
c0 = s.V3R;
c1 = s.V4R;
Vector4 t3 = c0 + c1;
Vector4 t4 = c0 - c1;
c0 = t0 + t3;
Vector4 c3 = t0 - t3;
c1 = t1 + t2;
Vector4 c2 = t1 - t2;
d.V0R = c0 + c1;
d.V4R = c0 - c1;
float w0 = 0.541196f;
float w1 = 1.306563f;
d.V2R = (w0 * c2) + (w1 * c3);
d.V6R = (w0 * c3) - (w1 * c2);
w0 = 1.175876f;
w1 = 0.785695f;
c3 = (w0 * t4) + (w1 * t7);
c0 = (w0 * t7) - (w1 * t4);
w0 = 1.387040f;
w1 = 0.275899f;
c2 = (w0 * t5) + (w1 * t6);
c1 = (w0 * t6) - (w1 * t5);
d.V3R = c0 - c2;
d.V5R = c3 - c1;
c0 = (c0 + c2) * InvSqrt2;
c3 = (c3 + c1) * InvSqrt2;
d.V1R = c0 + c3;
d.V7R = c0 - c3;
}
/// <summary>
/// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization)
/// </summary>
/// <param name="src">Source</param>
/// <param name="dest">Destination</param>
/// <param name="temp">Temporary block provided by the caller</param>
/// <param name="offsetSourceByNeg128">If true, a constant -128.0 offset is applied for all values before FDCT </param>
public static void TransformFDCT(
ref Block8x8F src,
ref Block8x8F dest,
ref Block8x8F temp,
bool offsetSourceByNeg128 = true)
{
src.TransposeInto(ref temp);
if (offsetSourceByNeg128)
{
temp.AddInPlace(-128F);
}
FDCT8x4_LeftPart(ref temp, ref dest);
FDCT8x4_RightPart(ref temp, ref dest);
dest.TransposeInto(ref temp);
FDCT8x4_LeftPart(ref temp, ref dest);
FDCT8x4_RightPart(ref temp, ref dest);
dest.MultiplyInPlace(C_0_125);
}
} }
} }

199
src/ImageSharp/Formats/Jpeg/Components/Quantization.cs

@ -0,0 +1,199 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
/// <summary>
/// Provides methods and properties related to jpeg quantization.
/// </summary>
internal static class Quantization
{
/// <summary>
/// Upper bound (inclusive) for jpeg quality setting.
/// </summary>
public const int MaxQualityFactor = 100;
/// <summary>
/// Lower bound (inclusive) for jpeg quality setting.
/// </summary>
public const int MinQualityFactor = 1;
/// <summary>
/// Default JPEG quality for both luminance and chominance tables.
/// </summary>
public const int DefaultQualityFactor = 75;
/// <summary>
/// Represents lowest quality setting which can be estimated with enough confidence.
/// Any quality below it results in a highly compressed jpeg image
/// which shouldn't use standard itu quantization tables for re-encoding.
/// </summary>
public const int QualityEstimationConfidenceLowerThreshold = 25;
/// <summary>
/// Represents highest quality setting which can be estimated with enough confidence.
/// </summary>
public const int QualityEstimationConfidenceUpperThreshold = 98;
/// <summary>
/// Gets unscaled luminance quantization table.
/// </summary>
/// <remarks>
/// The values are derived from ITU section K.1.
/// </remarks>
// 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
public static ReadOnlySpan<byte> LuminanceTable => new byte[]
{
16, 11, 10, 16, 24, 40, 51, 61,
12, 12, 14, 19, 26, 58, 60, 55,
14, 13, 16, 24, 40, 57, 69, 56,
14, 17, 22, 29, 51, 87, 80, 62,
18, 22, 37, 56, 68, 109, 103, 77,
24, 35, 55, 64, 81, 104, 113, 92,
49, 64, 78, 87, 103, 121, 120, 101,
72, 92, 95, 98, 112, 100, 103, 99,
};
/// <summary>
/// Gets unscaled chrominance quantization table.
/// </summary>
/// <remarks>
/// The values are derived from ITU section K.1.
/// </remarks>
// 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
public static ReadOnlySpan<byte> ChrominanceTable => new byte[]
{
17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
};
/// Ported from JPEGsnoop:
/// https://github.com/ImpulseAdventure/JPEGsnoop/blob/9732ee0961f100eb69bbff4a0c47438d5997abee/source/JfifDecode.cpp#L4570-L4694
/// <summary>
/// Estimates jpeg quality based on standard quantization table.
/// </summary>
/// <remarks>
/// Technically, this can be used with any given table but internal decoder code uses ITU spec tables:
/// <see cref="LuminanceTable"/> and <see cref="ChrominanceTable"/>.
/// </remarks>
/// <param name="table">Input quantization table.</param>
/// <param name="target">Natural order quantization table to estimate against.</param>
/// <returns>Estimated quality.</returns>
public static int EstimateQuality(ref Block8x8F table, ReadOnlySpan<byte> target)
{
// This method can be SIMD'ified if standard table is injected as Block8x8F.
// Or when we go to full-int16 spectral code implementation and inject both tables as Block8x8.
double comparePercent;
double sumPercent = 0;
// Corner case - all 1's => 100 quality
// It would fail to deduce using algorithm below without this check
if (table.EqualsToScalar(1))
{
// While this is a 100% to be 100 quality, any given table can be scaled to all 1's.
// According to jpeg creators, top of the line quality is 99, 100 is just a technical 'limit' which will affect result filesize drastically.
// Quality=100 shouldn't be used in usual use case.
return 100;
}
int quality;
for (int i = 0; i < Block8x8F.Size; i++)
{
int coeff = (int)table[i];
// Coefficients are actually int16 casted to float numbers so there's no truncating error.
if (coeff != 0)
{
comparePercent = 100.0 * (table[i] / target[i]);
}
else
{
// No 'valid' quantization table should contain zero at any position
// while this is okay to decode with, it will throw DivideByZeroException at encoding proces stage.
// Not sure what to do here, we can't throw as this technically correct
// but this will screw up the encoder.
comparePercent = 999.99;
}
sumPercent += comparePercent;
}
// Perform some statistical analysis of the quality factor
// to determine the likelihood of the current quantization
// table being a scaled version of the "standard" tables.
// If the variance is high, it is unlikely to be the case.
sumPercent /= 64.0;
// Generate the equivalent IJQ "quality" factor
if (sumPercent <= 100.0)
{
quality = (int)Math.Round((200 - sumPercent) / 2);
}
else
{
quality = (int)Math.Round(5000.0 / sumPercent);
}
return quality;
}
/// <summary>
/// Estimates jpeg quality based on quantization table in zig-zag order.
/// </summary>
/// <param name="luminanceTable">Luminance quantization table.</param>
/// <returns>Estimated quality</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int EstimateLuminanceQuality(ref Block8x8F luminanceTable)
=> EstimateQuality(ref luminanceTable, LuminanceTable);
/// <summary>
/// Estimates jpeg quality based on quantization table in zig-zag order.
/// </summary>
/// <param name="chrominanceTable">Chrominance quantization table.</param>
/// <returns>Estimated quality</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int EstimateChrominanceQuality(ref Block8x8F chrominanceTable)
=> EstimateQuality(ref chrominanceTable, ChrominanceTable);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int QualityToScale(int quality)
{
DebugGuard.MustBeBetweenOrEqualTo(quality, MinQualityFactor, MaxQualityFactor, nameof(quality));
return quality < 50 ? (5000 / quality) : (200 - (quality * 2));
}
private static Block8x8F ScaleQuantizationTable(int scale, ReadOnlySpan<byte> unscaledTable)
{
Block8x8F table = default;
for (int j = 0; j < Block8x8F.Size; j++)
{
int x = ((unscaledTable[j] * scale) + 50) / 100;
table[j] = Numerics.Clamp(x, 1, 255);
}
return table;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Block8x8F ScaleLuminanceTable(int quality)
=> ScaleQuantizationTable(scale: QualityToScale(quality), LuminanceTable);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Block8x8F ScaleChrominanceTable(int quality)
=> ScaleQuantizationTable(scale: QualityToScale(quality), ChrominanceTable);
}
}

300
src/ImageSharp/Formats/Jpeg/Components/ZigZag.Intrinsic.cs

@ -0,0 +1,300 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
#if SUPPORTS_RUNTIME_INTRINSICS
using System;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal static partial class ZigZag
{
#pragma warning disable SA1309 // naming rules violation warnings
/// <summary>
/// Special byte value to zero out elements during Sse/Avx shuffle intrinsics.
/// </summary>
private const byte _ = 0xff;
#pragma warning restore SA1309
/// <summary>
/// Gets shuffle vectors for <see cref="ApplyZigZagOrderingSsse3"/>
/// zig zag implementation.
/// </summary>
private static ReadOnlySpan<byte> SseShuffleMasks => new byte[]
{
// row0
0, 1, 2, 3, _, _, _, _, _, _, 4, 5, 6, 7, _, _,
_, _, _, _, 0, 1, _, _, 2, 3, _, _, _, _, 4, 5,
_, _, _, _, _, _, 0, 1, _, _, _, _, _, _, _, _,
// row1
_, _, _, _, _, _, _, _, _, _, _, _, 8, 9, 10, 11,
2, 3, _, _, _, _, _, _, 4, 5, _, _, _, _, _, _,
_, _, 0, 1, _, _, 2, 3, _, _, _, _, _, _, _, _,
// row2
_, _, _, _, _, _, 2, 3, _, _, _, _, _, _, 4, 5,
_, _, _, _, _, _, _, _, 0, 1, _, _, 2, 3, _, _,
// row3
_, _, _, _, _, _, 12, 13, 14, 15, _, _, _, _, _, _,
_, _, _, _, 10, 11, _, _, _, _, 12, 13, _, _, _, _,
_, _, 8, 9, _, _, _, _, _, _, _, _, 10, 11, _, _,
6, 7, _, _, _, _, _, _, _, _, _, _, _, _, 8, 9,
// row4
_, _, 4, 5, _, _, _, _, _, _, _, _, 6, 7, _, _,
_, _, _, _, 2, 3, _, _, _, _, 4, 5, _, _, _, _,
_, _, _, _, _, _, 0, 1, 2, 3, _, _, _, _, _, _,
// row5
_, _, 12, 13, _, _, 14, 15, _, _, _, _, _, _, _, _,
10, 11, _, _, _, _, _, _, 12, 13, _, _, _, _, _, _,
// row6
_, _, _, _, _, _, _, _, 12, 13, _, _, 14, 15, _, _,
_, _, _, _, _, _, 10, 11, _, _, _, _, _, _, 12, 13,
4, 5, 6, 7, _, _, _, _, _, _, _, _, _, _, _, _,
// row7
10, 11, _, _, _, _, 12, 13, _, _, 14, 15, _, _, _, _,
_, _, 8, 9, 10, 11, _, _, _, _, _, _, 12, 13, 14, 15
};
/// <summary>
/// Gets shuffle vectors for <see cref="ApplyZigZagOrderingAvx2"/>
/// zig zag implementation.
/// </summary>
private static ReadOnlySpan<byte> AvxShuffleMasks => new byte[]
{
// 01_AB/01_EF/23_CD - cross-lane
0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0,
// 01_AB - inner-lane
0, 1, 2, 3, 8, 9, _, _, 10, 11, 4, 5, 6, 7, 12, 13, _, _, _, _, _, _, _, _, _, _, 10, 11, 4, 5, 6, 7,
// 01_CD/23_GH - cross-lane
0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, _, _, _, _, 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, _, _, _, _,
// 01_CD - inner-lane
_, _, _, _, _, _, 0, 1, _, _, _, _, _, _, _, _, 2, 3, 8, 9, _, _, 10, 11, 4, 5, _, _, _, _, _, _,
// 01_EF - inner-lane
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 0, 1, _, _, _, _, _, _, _, _, _, _,
// 23_AB/45_CD/67_EF - cross-lane
3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, _, _, _, _, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, _, _, _, _,
// 23_AB - inner-lane
4, 5, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 6, 7, 0, 1, 2, 3, 8, 9, _, _, _, _,
// 23_CD - inner-lane
_, _, 6, 7, 12, 13, _, _, _, _, _, _, _, _, _, _, 10, 11, 4, 5, _, _, _, _, _, _, _, _, 6, 7, 12, 13,
// 23_EF - inner-lane
_, _, _, _, _, _, 2, 3, 8, 9, _, _, 10, 11, 4, 5, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
// 23_GH - inner-lane
_, _, _, _, _, _, _, _, _, _, 0, 1, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
// 45_AB - inner-lane
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 10, 11, _, _, _, _, _, _, _, _, _, _,
// 45_CD - inner-lane
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 6, 7, 0, 1, _, _, 2, 3, 8, 9, _, _, _, _, _, _,
// 45_EF - cross-lane
1, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, _, _, _, _, 2, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0,
// 45_EF - inner-lane
2, 3, 8, 9, _, _, _, _, _, _, _, _, 10, 11, 4, 5, _, _, _, _, _, _, _, _, _, _, 2, 3, 8, 9, _, _,
// 45_GH - inner-lane
_, _, _, _, 2, 3, 8, 9, 10, 11, 4, 5, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 6, 7,
// 67_CD - inner-lane
_, _, _, _, _, _, _, _, _, _, 10, 11, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
// 67_EF - inner-lane
_, _, _, _, _, _, 6, 7, 0, 1, _, _, 2, 3, 8, 9, _, _, _, _, _, _, _, _, 10, 11, _, _, _, _, _, _,
// 67_GH - inner-lane
8, 9, 10, 11, 4, 5, _, _, _, _, _, _, _, _, _, _, 2, 3, 8, 9, 10, 11, 4, 5, _, _, 6, 7, 12, 13, 14, 15
};
/// <summary>
/// Applies zig zag ordering for given 8x8 matrix using SSE cpu intrinsics.
/// </summary>
/// <param name="block">Input matrix.</param>
public static unsafe void ApplyZigZagOrderingSsse3(ref Block8x8 block)
{
DebugGuard.IsTrue(Ssse3.IsSupported, "Ssse3 support is required to run this operation!");
fixed (byte* maskPtr = SseShuffleMasks)
{
Vector128<byte> rowA = block.V0.AsByte();
Vector128<byte> rowB = block.V1.AsByte();
Vector128<byte> rowC = block.V2.AsByte();
Vector128<byte> rowD = block.V3.AsByte();
Vector128<byte> rowE = block.V4.AsByte();
Vector128<byte> rowF = block.V5.AsByte();
Vector128<byte> rowG = block.V6.AsByte();
Vector128<byte> rowH = block.V7.AsByte();
// row0 - A0 A1 B0 C0 B1 A2 A3 B2
Vector128<short> rowA0 = Ssse3.Shuffle(rowA, Sse2.LoadVector128(maskPtr + (16 * 0))).AsInt16();
Vector128<short> rowB0 = Ssse3.Shuffle(rowB, Sse2.LoadVector128(maskPtr + (16 * 1))).AsInt16();
Vector128<short> row0 = Sse2.Or(rowA0, rowB0);
Vector128<short> rowC0 = Ssse3.Shuffle(rowC, Sse2.LoadVector128(maskPtr + (16 * 2))).AsInt16();
row0 = Sse2.Or(row0, rowC0);
// row1 - C1 D0 E0 D1 C2 B3 A4 A5
Vector128<short> rowA1 = Ssse3.Shuffle(rowA, Sse2.LoadVector128(maskPtr + (16 * 3))).AsInt16();
Vector128<short> rowC1 = Ssse3.Shuffle(rowC, Sse2.LoadVector128(maskPtr + (16 * 4))).AsInt16();
Vector128<short> row1 = Sse2.Or(rowA1, rowC1);
Vector128<short> rowD1 = Ssse3.Shuffle(rowD, Sse2.LoadVector128(maskPtr + (16 * 5))).AsInt16();
row1 = Sse2.Or(row1, rowD1);
row1 = Sse2.Insert(row1.AsUInt16(), Sse2.Extract(rowB.AsUInt16(), 3), 5).AsInt16();
row1 = Sse2.Insert(row1.AsUInt16(), Sse2.Extract(rowE.AsUInt16(), 0), 2).AsInt16();
// row2
Vector128<short> rowE2 = Ssse3.Shuffle(rowE, Sse2.LoadVector128(maskPtr + (16 * 6))).AsInt16();
Vector128<short> rowF2 = Ssse3.Shuffle(rowF, Sse2.LoadVector128(maskPtr + (16 * 7))).AsInt16();
Vector128<short> row2 = Sse2.Or(rowE2, rowF2);
row2 = Sse2.Insert(row2.AsUInt16(), Sse2.Extract(rowB.AsUInt16(), 4), 0).AsInt16();
row2 = Sse2.Insert(row2.AsUInt16(), Sse2.Extract(rowC.AsUInt16(), 3), 1).AsInt16();
row2 = Sse2.Insert(row2.AsUInt16(), Sse2.Extract(rowD.AsUInt16(), 2), 2).AsInt16();
row2 = Sse2.Insert(row2.AsUInt16(), Sse2.Extract(rowG.AsUInt16(), 0), 5).AsInt16();
// row3
Vector128<short> rowA3 = Ssse3.Shuffle(rowA, Sse2.LoadVector128(maskPtr + (16 * 8))).AsInt16().AsInt16();
Vector128<short> rowB3 = Ssse3.Shuffle(rowB, Sse2.LoadVector128(maskPtr + (16 * 9))).AsInt16().AsInt16();
Vector128<short> row3 = Sse2.Or(rowA3, rowB3);
Vector128<short> rowC3 = Ssse3.Shuffle(rowC, Sse2.LoadVector128(maskPtr + (16 * 10))).AsInt16();
row3 = Sse2.Or(row3, rowC3);
Vector128<byte> shuffleRowD3EF = Sse2.LoadVector128(maskPtr + (16 * 11));
Vector128<short> rowD3 = Ssse3.Shuffle(rowD, shuffleRowD3EF).AsInt16();
row3 = Sse2.Or(row3, rowD3);
// row4
Vector128<short> rowE4 = Ssse3.Shuffle(rowE, shuffleRowD3EF).AsInt16();
Vector128<short> rowF4 = Ssse3.Shuffle(rowF, Sse2.LoadVector128(maskPtr + (16 * 12))).AsInt16();
Vector128<short> row4 = Sse2.Or(rowE4, rowF4);
Vector128<short> rowG4 = Ssse3.Shuffle(rowG, Sse2.LoadVector128(maskPtr + (16 * 13))).AsInt16();
row4 = Sse2.Or(row4, rowG4);
Vector128<short> rowH4 = Ssse3.Shuffle(rowH, Sse2.LoadVector128(maskPtr + (16 * 14))).AsInt16();
row4 = Sse2.Or(row4, rowH4);
// row5
Vector128<short> rowC5 = Ssse3.Shuffle(rowC, Sse2.LoadVector128(maskPtr + (16 * 15))).AsInt16();
Vector128<short> rowD5 = Ssse3.Shuffle(rowD, Sse2.LoadVector128(maskPtr + (16 * 16))).AsInt16();
Vector128<short> row5 = Sse2.Or(rowC5, rowD5);
row5 = Sse2.Insert(row5.AsUInt16(), Sse2.Extract(rowB.AsUInt16(), 7), 2).AsInt16();
row5 = Sse2.Insert(row5.AsUInt16(), Sse2.Extract(rowE.AsUInt16(), 5), 5).AsInt16();
row5 = Sse2.Insert(row5.AsUInt16(), Sse2.Extract(rowF.AsUInt16(), 4), 6).AsInt16();
row5 = Sse2.Insert(row5.AsUInt16(), Sse2.Extract(rowG.AsUInt16(), 3), 7).AsInt16();
// row6
Vector128<short> rowE6 = Ssse3.Shuffle(rowE, Sse2.LoadVector128(maskPtr + (16 * 17))).AsInt16();
Vector128<short> rowF6 = Ssse3.Shuffle(rowF, Sse2.LoadVector128(maskPtr + (16 * 18))).AsInt16();
Vector128<short> row6 = Sse2.Or(rowE6, rowF6);
Vector128<short> rowH6 = Ssse3.Shuffle(rowH, Sse2.LoadVector128(maskPtr + (16 * 19))).AsInt16();
row6 = Sse2.Or(row6, rowH6);
row6 = Sse2.Insert(row6.AsUInt16(), Sse2.Extract(rowD.AsUInt16(), 7), 5).AsInt16();
row6 = Sse2.Insert(row6.AsUInt16(), Sse2.Extract(rowG.AsUInt16(), 4), 2).AsInt16();
// row7
Vector128<short> rowG7 = Ssse3.Shuffle(rowG, Sse2.LoadVector128(maskPtr + (16 * 20))).AsInt16();
Vector128<short> rowH7 = Ssse3.Shuffle(rowH, Sse2.LoadVector128(maskPtr + (16 * 21))).AsInt16();
Vector128<short> row7 = Sse2.Or(rowG7, rowH7);
row7 = Sse2.Insert(row7.AsUInt16(), Sse2.Extract(rowF.AsUInt16(), 7), 4).AsInt16();
block.V0 = row0;
block.V1 = row1;
block.V2 = row2;
block.V3 = row3;
block.V4 = row4;
block.V5 = row5;
block.V6 = row6;
block.V7 = row7;
}
}
/// <summary>
/// Applies zig zag ordering for given 8x8 matrix using AVX cpu intrinsics.
/// </summary>
/// <param name="block">Input matrix.</param>
public static unsafe void ApplyZigZagOrderingAvx2(ref Block8x8 block)
{
DebugGuard.IsTrue(Avx2.IsSupported, "Avx2 support is required to run this operation!");
fixed (byte* shuffleVectorsPtr = AvxShuffleMasks)
{
Vector256<byte> rowsAB = block.V01.AsByte();
Vector256<byte> rowsCD = block.V23.AsByte();
Vector256<byte> rowsEF = block.V45.AsByte();
Vector256<byte> rowsGH = block.V67.AsByte();
// rows 0 1
Vector256<int> rows_AB01_EF01_CD23_shuffleMask = Avx.LoadVector256(shuffleVectorsPtr + (0 * 32)).AsInt32();
Vector256<byte> row01_AB = Avx2.PermuteVar8x32(rowsAB.AsInt32(), rows_AB01_EF01_CD23_shuffleMask).AsByte();
row01_AB = Avx2.Shuffle(row01_AB, Avx.LoadVector256(shuffleVectorsPtr + (1 * 32))).AsByte();
Vector256<int> rows_CD01_GH23_shuffleMask = Avx.LoadVector256(shuffleVectorsPtr + (2 * 32)).AsInt32();
Vector256<byte> row01_CD = Avx2.PermuteVar8x32(rowsCD.AsInt32(), rows_CD01_GH23_shuffleMask).AsByte();
row01_CD = Avx2.Shuffle(row01_CD, Avx.LoadVector256(shuffleVectorsPtr + (3 * 32))).AsByte();
Vector256<byte> row0123_EF = Avx2.PermuteVar8x32(rowsEF.AsInt32(), rows_AB01_EF01_CD23_shuffleMask).AsByte();
Vector256<byte> row01_EF = Avx2.Shuffle(row0123_EF, Avx.LoadVector256(shuffleVectorsPtr + (4 * 32))).AsByte();
Vector256<byte> row01 = Avx2.Or(Avx2.Or(row01_AB, row01_CD), row01_EF);
// rows 2 3
Vector256<int> rows_AB23_CD45_EF67_shuffleMask = Avx.LoadVector256(shuffleVectorsPtr + (5 * 32)).AsInt32();
Vector256<byte> row2345_AB = Avx2.PermuteVar8x32(rowsAB.AsInt32(), rows_AB23_CD45_EF67_shuffleMask).AsByte();
Vector256<byte> row23_AB = Avx2.Shuffle(row2345_AB, Avx.LoadVector256(shuffleVectorsPtr + (6 * 32))).AsByte();
Vector256<byte> row23_CD = Avx2.PermuteVar8x32(rowsCD.AsInt32(), rows_AB01_EF01_CD23_shuffleMask).AsByte();
row23_CD = Avx2.Shuffle(row23_CD, Avx.LoadVector256(shuffleVectorsPtr + (7 * 32))).AsByte();
Vector256<byte> row23_EF = Avx2.Shuffle(row0123_EF, Avx.LoadVector256(shuffleVectorsPtr + (8 * 32))).AsByte();
Vector256<byte> row2345_GH = Avx2.PermuteVar8x32(rowsGH.AsInt32(), rows_CD01_GH23_shuffleMask).AsByte();
Vector256<byte> row23_GH = Avx2.Shuffle(row2345_GH, Avx.LoadVector256(shuffleVectorsPtr + (9 * 32)).AsByte());
Vector256<byte> row23 = Avx2.Or(Avx2.Or(row23_AB, row23_CD), Avx2.Or(row23_EF, row23_GH));
// rows 4 5
Vector256<byte> row45_AB = Avx2.Shuffle(row2345_AB, Avx.LoadVector256(shuffleVectorsPtr + (10 * 32)).AsByte());
Vector256<byte> row4567_CD = Avx2.PermuteVar8x32(rowsCD.AsInt32(), rows_AB23_CD45_EF67_shuffleMask).AsByte();
Vector256<byte> row45_CD = Avx2.Shuffle(row4567_CD, Avx.LoadVector256(shuffleVectorsPtr + (11 * 32)).AsByte());
Vector256<int> rows_EF45_GH67_shuffleMask = Avx.LoadVector256(shuffleVectorsPtr + (12 * 32)).AsInt32();
Vector256<byte> row45_EF = Avx2.PermuteVar8x32(rowsEF.AsInt32(), rows_EF45_GH67_shuffleMask).AsByte();
row45_EF = Avx2.Shuffle(row45_EF, Avx.LoadVector256(shuffleVectorsPtr + (13 * 32)).AsByte());
Vector256<byte> row45_GH = Avx2.Shuffle(row2345_GH, Avx.LoadVector256(shuffleVectorsPtr + (14 * 32)).AsByte());
Vector256<byte> row45 = Avx2.Or(Avx2.Or(row45_AB, row45_CD), Avx2.Or(row45_EF, row45_GH));
// rows 6 7
Vector256<byte> row67_CD = Avx2.Shuffle(row4567_CD, Avx.LoadVector256(shuffleVectorsPtr + (15 * 32)).AsByte());
Vector256<byte> row67_EF = Avx2.PermuteVar8x32(rowsEF.AsInt32(), rows_AB23_CD45_EF67_shuffleMask).AsByte();
row67_EF = Avx2.Shuffle(row67_EF, Avx.LoadVector256(shuffleVectorsPtr + (16 * 32)).AsByte());
Vector256<byte> row67_GH = Avx2.PermuteVar8x32(rowsGH.AsInt32(), rows_EF45_GH67_shuffleMask).AsByte();
row67_GH = Avx2.Shuffle(row67_GH, Avx.LoadVector256(shuffleVectorsPtr + (17 * 32)).AsByte());
Vector256<byte> row67 = Avx2.Or(Avx2.Or(row67_CD, row67_EF), row67_GH);
block.V01 = row01.AsInt16();
block.V23 = row23.AsInt16();
block.V45 = row45.AsInt16();
block.V67 = row67.AsInt16();
}
}
}
}
#endif

79
src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs

@ -2,21 +2,15 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
/// <summary> internal static partial class ZigZag
/// Holds the Jpeg UnZig array in a value/stack type.
/// Unzig maps from the zigzag ordering to the natural ordering. For example,
/// unzig[3] is the column and row of the fourth element in zigzag order. The
/// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct ZigZag
{ {
/// <summary> /// <summary>
/// Gets span of zig-zag ordering indices.
/// </summary>
/// <remarks>
/// When reading corrupted data, the Huffman decoders could attempt /// When reading corrupted data, the Huffman decoders could attempt
/// to reference an entry beyond the end of this array (if the decoded /// to reference an entry beyond the end of this array (if the decoded
/// zero run length reaches past the end of the block). To prevent /// zero run length reaches past the end of the block). To prevent
@ -25,20 +19,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// to be stored in location 63 of the block, not somewhere random. /// to be stored in location 63 of the block, not somewhere random.
/// The worst case would be a run-length of 15, which means we need 16 /// The worst case would be a run-length of 15, which means we need 16
/// fake entries. /// fake entries.
/// </summary> /// </remarks>
private const int Size = 64 + 16; public static ReadOnlySpan<byte> ZigZagOrder => new byte[]
/// <summary>
/// Copy of <see cref="Unzig"/> in a value type
/// </summary>
public fixed byte Data[Size];
/// <summary>
/// Gets the unzigs map, which maps from the zigzag ordering to the natural ordering.
/// For example, unzig[3] is the column and row of the fourth element in zigzag order.
/// The value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
/// </summary>
private static ReadOnlySpan<byte> Unzig => new byte[]
{ {
0, 1, 8, 16, 9, 2, 3, 10, 0, 1, 8, 16, 9, 2, 3, 10,
17, 24, 32, 25, 18, 11, 4, 5, 17, 24, 32, 25, 18, 11, 4, 5,
@ -48,53 +30,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
29, 22, 15, 23, 30, 37, 44, 51, 29, 22, 15, 23, 30, 37, 44, 51,
58, 59, 52, 45, 38, 31, 39, 46, 58, 59, 52, 45, 38, 31, 39, 46,
53, 60, 61, 54, 47, 55, 62, 63, 53, 60, 61, 54, 47, 55, 62, 63,
63, 63, 63, 63, 63, 63, 63, 63, // Extra entries for safety in decoder
// Extra entries for safety in decoder
63, 63, 63, 63, 63, 63, 63, 63,
63, 63, 63, 63, 63, 63, 63, 63 63, 63, 63, 63, 63, 63, 63, 63
}; };
/// <summary>
/// Returns the value at the given index
/// </summary>
/// <param name="idx">The index</param>
/// <returns>The <see cref="byte"/></returns>
public byte this[int idx]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
ref byte self = ref Unsafe.As<ZigZag, byte>(ref this);
return Unsafe.Add(ref self, idx);
}
}
/// <summary>
/// Creates and fills an instance of <see cref="ZigZag"/> with Jpeg unzig indices
/// </summary>
/// <returns>The new instance</returns>
public static ZigZag CreateUnzigTable()
{
ZigZag result = default;
ref byte sourceRef = ref MemoryMarshal.GetReference(Unzig);
ref byte destinationRef = ref Unsafe.AsRef<byte>(result.Data);
Unzig.CopyTo(new Span<byte>(result.Data, Size));
return result;
}
/// <summary>
/// Apply Zigging to the given quantization table, so it will be sufficient to multiply blocks for dequantizing them.
/// </summary>
public static Block8x8F CreateDequantizationTable(ref Block8x8F qt)
{
Block8x8F result = default;
for (int i = 0; i < Block8x8F.Size; i++)
{
result[Unzig[i]] = qt[i];
}
return result;
}
} }
} }

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

@ -9,20 +9,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
internal interface IJpegEncoderOptions internal interface IJpegEncoderOptions
{ {
/// <summary> /// <summary>
/// Gets the quality, that will be used to encode the image. Quality /// Gets or sets the quality, that will be used to encode the image. Quality
/// index must be between 0 and 100 (compression from max to min). /// index must be between 0 and 100 (compression from max to min).
/// Defaults to <value>75</value>.
/// </summary> /// </summary>
/// <value>The quality of the jpg image from 0 to 100.</value> public int? Quality { get; set; }
int? Quality { get; }
/// <summary> /// <summary>
/// Gets the subsample ration, that will be used to encode the image. /// Gets the color type, that will be used to encode the image.
/// </summary>
/// <value>The subsample ratio of the jpg image.</value>
JpegSubsample? Subsample { get; }
/// <summary>
/// Gets the color type.
/// </summary> /// </summary>
JpegColorType? ColorType { get; } JpegColorType? ColorType { get; }
} }

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

Loading…
Cancel
Save